Modules

The following snippet shows how to iterate over the modules loaded in the current process and how to access common attributes such as the imagebase or the name:

for mod in lief.runtime.modules():
    print(mod)
    # Look for the libc
    if mod.name.startswith("libc.so"):
        print(
            f"libc.so found: {mod.path} ([{mod.imagebase:#010x}, {mod.end:#010x}])"
        )
        assert isinstance(mod, lief.runtime.linux.Module)
        libc = mod

Linux

On Linux, extends the generic interface with platform-specific helpers. In particular, it exposes the & and can parse the module directly from its path on disk or from its in-memory representation. The following snippet demonstrates these operations on the libc module:
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)
    # lief.to_int is used to convert an opaque pointer (void*) into a
    # regular Python int
    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)

# This parses the ELF from its path on the disk
if elf_on_disk := libc.parse_from_path():
    if __cxa_finalize := elf_on_disk.get_symbol("__cxa_finalize"):
        print(f"__cxa_finalize: {__cxa_finalize.value:#010x}")

# This code loads 'librt.so' and wrap the dlopen handler in a Module.
if librt := lief.runtime.linux.dlopen("librt.so.1"):
    print(
        f"librt loaded: {librt.path} (dlopen handler={lief.to_int(librt.handle):#010x})"
    )

    # Parse the ELF directly from memory.
    if librt_mem := librt.parse_from_memory():
        # This code looks for the relocation associated with the
        # **imported** symbol `__cxa_finalize` that is defined in the libc.
        # The relocation holds the address where the function is resolved.
        # Therefore, by reading this relocation address we can access the
        # resolved address of `__cxa_finalize`. This address should match
        # the value previously returned by dlsym
        __cxa_finalize_reloc = next(
            (
                reloc
                for reloc in librt_mem.relocations
                if reloc.symbol is not None
                and reloc.symbol.name == "__cxa_finalize"
            ),
            None,
        )
        if __cxa_finalize_reloc is not None:
            abs_reloc_addr = librt.imagebase + __cxa_finalize_reloc.address
            # We assume a 64-bit architecture
            __cxa_finalize_addr = lief.runtime.Memory.read_u64(abs_reloc_addr)
            print(
                f"{librt.name} -> {abs_reloc_addr:#010x} -> {__cxa_finalize_addr:#010x}"
            )

Windows

On Windows, you can perform similar operations such as in-memory parsing, accessing the HMODULE handle, and resolving functions through a dlsym-like helper:

print(f"ntdll path: {ntdll.path}")
if nt_query := ntdll.dlsym("NtQueryInformationProcess"):
    # lief.to_int converts an opaque pointer (void*) into a regular Python int
    nt_query_addr = lief.to_int(nt_query)
    print(f"{ntdll.name}!NtQueryInformationProcess: {nt_query_addr:#010x}")

# This parses the PE from its path on the disk
if pe_on_disk := ntdll.parse_from_path():
    print(f"on-disk imagebase: {pe_on_disk.optional_header.imagebase:#010x}")
    if sym := pe_on_disk.get_symbol("NtQueryInformationProcess"):
        print(f"NtQueryInformationProcess: {sym.value:#010x}")

# This parses the PE directly from memory
if pe_memory := ntdll.parse_from_memory():
    imagebase = pe_memory.optional_header.imagebase
    print(f"in-memory imagebase: {imagebase:#010x}")
    if sym := pe_memory.get_symbol("RtlFreeHeap"):
        print(f"RtlFreeHeap: {sym.value:#010x}")

        # With LIEF extended, we can disassemble the function directly from
        # its absolute memory address:
        if lief.__extended__:
            for inst in islice(lief.runtime.disassemble(imagebase + sym.value), 4):
                print("   ", inst)

OSX

print(f"libsystem_c path: {libsystem.path}")
if arc4_init := libsystem.dlsym("arc4_init"):
    # lief.to_int converts an opaque pointer (void*) into a regular Python int
    arc4_init_addr = lief.to_int(arc4_init)
    print(f"{libsystem.name}!arc4_init: {arc4_init_addr:#010x}")

    # With LIEF extended, we can disassemble the function directly from its
    # absolute memory address:
    if lief.__extended__:
        for inst in islice(lief.runtime.disassemble(arc4_init_addr), 4):
            print("   ", inst)

# libsystem.path looks like a valid path but libsystem_c.dylib lives in the
# dyld-shared-cache. Therefore, it's pointless to try to parse the library
# from its filepath as the library does not exist on the disk.
macho_on_disk: lief.MachO.Binary | None = None
with lief.logging.level_scope(lief.logging.LEVEL.OFF):
    macho_on_disk = libsystem.parse_from_path()

if macho_on_disk is None:
    print(f"As expected, {libsystem.path} is not present in the filesystem")

# But we can parse it from memory:
if macho_memory := libsystem.parse_from_memory():
    print(f"in-memory imagebase: {macho_memory.imagebase:#010x}")
    # List 'exported' symbols
    for sym in macho_memory.exported_symbols:
        print(f"{sym.name:10}: {sym.value:#010x}")

Android

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():
    if __cxa_finalize := elf_on_disk.get_symbol("__cxa_finalize"):
        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}")