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];
""")
auto elf = LIEF::ELF::Parser::parse("/bin/hello");
std::vector<uint64_t> syscall_addresses =
elf->disassemble(0x400090)
| std::view::filter([] (const std::unique_ptr<LIEF::assembly::Instruction> I) {
return I->is_syscall();
})
| std::view::transform([] (const std::unique_ptr<LIEF::assembly::Instruction> I) {
return I->address();
})
| std::ranges::to<std::vector>();
for (uint64_t addr : syscall_addresses) {
elf->assemble(addr, R"asm(
mov x1, x0;
str x1, [x2, #8];
)asm");
}
let mut elf = lief::elf::Binary::parse("/bin/hello");
let syscall_addresses =
elf.disassemble(0x400090)
.filter(|I| I.is_syscall())
.transform(|I| I.address())
.collect::<Vec<u64>>();
for addr in syscall_addresses {
elf.assemble(addr, r#"
mov x1, x0;
str x1, [x2, #8];
"#);
}
Warning
The assembler works well for AArch64/ARM64E and x86/x86-64, but support for other architectures is currently limited.
Similar to the 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.
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 patch:
import lief
elf = lief.ELF.parse("/bin/ssh")
elf.assemble(elf.entrypoint, """
mov rdi, rax;
call a_custom_function
""")
auto elf = LIEF::ELF::Parser::parse("/bin/ssh");
elf->assemble(elf->entrypoint(), R"asm(
mov rdi, rax;
call a_custom_function;
)asm");
let mut elf = lief::elf::Binary::parse("/bin/ssh");
elf.assemble(addr, r#"
mov rdi, rax;
call a_custom_function;
"#);
In this example, a_custom_function is undefined, so the assembler engine cannot resolve it and raises the following error:
warning: Fixup not resolved:
call a_custom_function
import lief
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.parse("/bin/ssh")
elf.assemble(elf.entrypoint, """
mov rdi, rax;
call a_custom_function
""", MyConfig())
class MyConfig : public LIEF::assembly::AssemblerConfig {
public:
LIEF::optional<uint64_t> resolve_symbol(const std::string& name) const override {
if (name == "a_custom_function") {
return 0x1000;
}
return LIEF::nullopt();
}
};
auto elf = LIEF::ELF::Parser::parse("/bin/ssh");
MyConfig myconfig;
elf->assemble(elf->entrypoint(), R"asm(
mov rdi, rax;
call a_custom_function;
)asm", myconfig);
let mut elf = lief::elf::Binary::parse("/bin/ssh");
let mut config = lief::assembly::AssemblerConfig::default();
let resolver = Arc::new(move |symbol: &str| {
if symbol == "a_custom_function" {
return Some(0x1000);
}
None
});
config.symbol_resolver = Some(resolver);
elf.assemble(addr, r#"
mov rdi, rax;
call a_custom_function;
"#, &config);
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.parse("/bin/ssh")
config = MyConfig(elf)
elf.assemble(elf.entrypoint, """
mov rdi, rax;
call a_custom_function
""", config)
class MyConfig : public LIEF::assembly::AssemblerConfig {
public:
MyConfig() = delete;
MyConfig(LIEF::Binary& target) :
LIEF::assembly::AssemblerConfig()
{
target_ = ⌖
}
LIEF::optional<uint64_t> resolve_symbol(const std::string& name) const override {
if (auto addr = target_->get_function_address(name)) {
return *addr;
}
return LIEF::nullopt();
}
~MyConfig() override = default;
private:
LIEF::Binary* target_ = nullptr;
};
auto elf = LIEF::ELF::Parser::parse("/bin/ssh");
MyConfig myconfig(*elf);
elf->assemble(elf->entrypoint(), R"asm(
mov rdi, rax;
call a_custom_function;
)asm", myconfig);
lief::assembly::AssemblerConfig::symbol_resolver can capture most of its context:let mut elf = lief::elf::Binary::parse("/bin/ssh");
let mut config = lief::assembly::AssemblerConfig::default();
let mut sym_map = HashMap::new();
for sym in ls.exported_symbols() {
sym_map.insert(sym.name(), sym.value());
}
let resolver = Arc::new(move |symbol: &str| {
sym_map.get(symbol).copied()
});
config.symbol_resolver = Some(resolver);
elf.assemble(addr, r#"
mov rdi, rax;
call a_custom_function;
"#, &config);