Files
2026-08-01 18:23:49 +02:00

196 lines
5.7 KiB
Python
Executable File

#!/usr/bin/env python3
import ctypes
import sys
from itertools import islice
import lief
def host_info():
print("Host")
# lief-doc: host-start
print(f" Hostname: {lief.runtime.Host.name}")
print(f" Home: {lief.runtime.Host.home_dir}")
print(f" cache: {lief.runtime.Host.cache_dir}")
print(f" temp: {lief.runtime.Host.tmp_dir}")
print(f" config: {lief.runtime.Host.config_dir}")
# lief-doc: host-end
# lief-doc: host-android-start
if (sdk := lief.runtime.android.Host.sdk_version) is not None:
print(f" SDK: {sdk}")
# lief-doc: host-android-end
print()
def process_info():
print("Process")
# lief-doc: process-start
print(f" PID: {lief.runtime.Process.pid}")
print(f" TID: {lief.runtime.Process.tid}")
print(f" Page size: {lief.runtime.Process.page_size:#06x}")
print(f" Arch: {lief.runtime.Process.arch}")
print(f" Platform: {lief.runtime.Process.platform}")
# lief-doc: process-end
# lief-doc: process-android-start
# Access a named property
sdk = lief.runtime.android.Process.get_system_property("ro.build.version.sdk")
if sdk is not None:
print(f" {sdk.name}: {sdk.value} (serial: {sdk.serial})")
# Iterate over all properties
for prop in lief.runtime.android.Process.properties:
print(f" {prop.name}: {prop.value} (serial: {prop.serial})")
# lief-doc: process-android-end
def modules_info():
print("Module API")
libc: lief.runtime.android.Module | None = None
# lief-doc: modules-start
for mod in lief.runtime.modules():
print(mod)
# Look for the Bionic libc
if mod.name.endswith("libc.so"):
print(
f"libc.so found: {mod.path} ([{mod.imagebase:#010x}, {mod.end:#010x}])"
)
assert isinstance(mod, lief.runtime.android.Module)
libc = mod
# lief-doc: modules-end
if libc is None:
return
# lief-doc: modules-android-start
print(f"libc path: {libc.path}")
if __cxa_finalize := libc.dlsym("__cxa_finalize"):
__cxa_finalize_addr = lief.to_int(__cxa_finalize)
print(f"{libc.name}!__cxa_finalize: {__cxa_finalize_addr:#010x}")
if malloc := libc.dlsym("malloc"):
malloc_addr = lief.to_int(malloc)
print(f"{libc.name}!malloc: {malloc_addr:#010x}")
# With LIEF extended, we can disassemble the function from its
# absolute memory address:
if lief.__extended__:
for inst in islice(lief.runtime.disassemble(malloc_addr), 4):
print(" ", inst)
if (elf_on_disk := libc.parse_from_path()) is not None and (
__cxa_finalize := elf_on_disk.get_symbol("__cxa_finalize")
) is not None:
print(f"__cxa_finalize: {__cxa_finalize.value:#010x}")
if liblog := lief.runtime.android.dlopen("liblog.so"):
print(
f"liblog loaded: {liblog.path} "
f"(dlopen handler={lief.to_int(liblog.handle):#010x})"
)
if log_print := liblog.dlsym("__android_log_print"):
log_print_addr = lief.to_int(log_print)
print(f"{liblog.name}!__android_log_print: {log_print_addr:#010x}")
# lief-doc: modules-android-end
def memory_example():
print("Memory")
# lief-doc: memory-start
chunk = lief.runtime.Memory.mmap(
lief.runtime.Process.page_size,
lief.runtime.Memory.ANONYMOUS | lief.runtime.Memory.PRIVATE,
lief.runtime.Memory.READ | lief.runtime.Memory.WRITE | lief.runtime.Memory.EXEC,
)
class Config(lief.assembly.AssemblerConfig):
def resolve_symbol(self, name: str) -> int | None:
libc = lief.runtime.android.dlopen("libc.so")
if libc is None:
print("Failed to dlopen libc.so", file=sys.stderr)
return None
if name == "write":
return lief.to_int(libc.dlsym("write"))
return None
config = Config()
raw_inst = lief.runtime.assemble(
chunk.addr,
r"""
.text
.global main
.align 2
main:
stp x29, x30, [sp, -16]!
mov x29, sp
ldr x8, =write
mov x0, 1 // STDOUT_FILENO
adr x1, msg // buf = &msg
mov x2, 27 // len = len("LIEF Runtime Extended Demo\n")
blr x8 // write(STDOUT_FILENO, msg, 27)
ldp x29, x30, [sp], 16
ret
msg:
.ascii "LIEF Runtime Extended Demo\n"
""",
config,
)
# Disassemble the JITed stub from memory
for inst in islice(lief.runtime.disassemble(chunk.addr), 9):
print(inst)
# Dump as raw bytes
raw_bytes = lief.runtime.Memory.read(chunk.addr, len(raw_inst))
print(raw_bytes.hex(":"))
# Flush the instruction cache before executing the freshly written code
# (this is required on AArch64).
chunk.cache_flush()
# Change the permission to R-X
chunk.make_rx()
VoidVoidFunc = ctypes.CFUNCTYPE(None) # void(*)()
hello_jit = VoidVoidFunc(chunk.addr)
hello_jit()
# Change the message
pos = raw_bytes.find(b"LIEF Runtime")
chunk.make_rw()
lief.runtime.Memory.write(b"Hello Python ctypes World!\n", chunk.addr + pos)
chunk.make_rx()
chunk.cache_flush()
# Print again
hello_jit()
# Don't miss good practices!
chunk.deallocate()
# lief-doc: memory-end
def main() -> int:
if not lief.runtime.enabled:
print("Error: LIEF's runtime is not enabled", file=sys.stderr)
return 0
host_info()
process_info()
modules_info()
memory_example()
return 0
if __name__ == "__main__":
sys.exit(main())