object

repository·main·Indexed 21 days ago

https://github.com/gimli-rs/object

A unified, cross-platform interface for reading and writing object file formats, including ELF, Mach-O, PE/COFF, Wasm, and XCOFF. It provides three levels of abstraction: raw struct definitions for zero-copy access, low-level APIs, and a high-level unified API for common features like sections and symbols. The project also includes a rewrite crate and CLI tool for modifying ELF files, allowing for symbol and section manipulation, as well as management of dynamic section entries such as DT_NEEDED, DT_RPATH, and DT_SONAME.

Tokens
22.5K
Snippets
68
Records
93
Agent score
74%

What's inside object

  1. Overview of the `object` crate

    main

    The object crate provides a unified interface for working with object files across multiple platforms. It supports reading relocatable object files and executables, as well as writing various formats.

    Supported Formats for Reading

    • ELF
    • Mach-O
    • Windows PE/COFF
    • Wasm
    • XCOFF
    • Unix archive

    Supported Formats for Writing

    • Relocatable Object Files: ELF, Mach-O, COFF, XCOFF (via a unified API)
    • Executables: ELF, PE
    • Low-level writers: ELF, PE, and COFF
    • High-level builder: ELF (available in the rewrite crate)
  2. Levels of support for reading object files

    main

    The crate provides three distinct levels of abstraction for reading object files, allowing you to choose between performance and ease of use:

    1. Raw struct definitions: Suitable for zero-copy access to the file data.
    2. Low-level APIs: Provides direct access to the raw structs.
    3. High-level unified API: A consumer-friendly API for accessing common features like sections and symbols across different file formats.
  3. Security considerations and limitations

    main

    The object crate is designed for use with trusted inputs (e.g., as part of a compiler toolchain). It is not recommended for malware analysis or services exposed to arbitrary, untrusted user input.

    Key Security Properties

    • Memory Safety: The crate provides memory safety, using unsafe only where necessary for performance.
    • Error Handling: Malformed input is expected to return an Error rather than causing a panic or incorrect parsing.

    Known Limitations

    • DoS Vulnerability: There is limited mitigation against Denial of Service attacks, such as algorithmic complexity attacks caused by overlapping structures.
    • Loader Divergence: The crate does not aim to replicate the exact behavior of platform loaders (like the Windows loader or dynamic linkers). The data returned by the parser may differ from what a system loader uses.
  4. What is the Pod trait and how to use it?

    main

    The Pod (Plain Old Data) trait is an unsafe marker trait for types that can be safely converted to and from byte slices via zero-copy casting.

    To implement Pod for a custom type, the type must satisfy these safety requirements:

    • It must be marked #[repr(C)] or #[repr(transparent)].
    • It must have no invalid byte values.
    • It must have no padding.

    Common types like u8, u16, u32, u64, and arrays of Pod types already implement this trait.

  5. Build object files using the `build` API

    main
    The build module provides helpers for constructing object files. You can use these tools to either build new object files from scratch or to modify existing ones. This is a higher-level abstraction than the raw write APIs.
  6. How the COFF Writer works

    main

    The Writer uses a two-phase approach to construct COFF files:

    1. Phase 1: Reservation: You build up all information that needs to be known ahead of time. This includes building the string table, reserving section indices, reserving symbol indices, and reserving file ranges for headers and sections.

      • Ordering Requirement: Strings must be added to the string table before reserving the file range for the string table.
      • Ordering Requirement: Symbol and string table reservations must happen after reserving symbol indices or adding strings, but before writing them out.
    2. Phase 2: Writing: You write everything out in order. The caller must ensure that the writing sequence matches the order in which file ranges were reserved.

    Use reserved_len() to check the current reserved file length and len() to check the current written length.

  7. Manage string tables with `StringTable`

    main

    The StringTable struct manages a collection of null-terminated byte strings used when writing object files. It handles deduplication and can optimize the table by performing suffix merging (where a string that is a suffix of another string is placed immediately after it to save space).

    There are two modes of operation:

    1. Default Mode: Performs suffix merging to optimize space. This requires calling write before retrieving offsets via get_offset.
    2. In-Order Mode: Strings are written in the exact order they are added. This mode does not perform suffix merging and allows retrieving offsets via get_offset immediately after calling add.

    Constraints:

    • Strings must not contain null bytes (0).
    • In default mode, get_offset will panic if write has not been called.
    • The total size of the string table must not exceed u32::MAX when writing.
    use gimli_object::write::StringTable;
    
    let mut table = StringTable::new();
    table.add(b"foo");
    // ... write to buffer ...
    let offset = table.get_offset(id);
  8. Handle separate .DBG files with ImageSeparateDebugHeader

    main

    When an image has the IMAGE_FILE_DEBUG_STRIPPED flag set, debugging information may be located in a separate .DBG file. The beginning of this file contains an ImageSeparateDebugHeader.

    This header allows a debugger to proceed even if the original image is inaccessible by providing metadata like the image_base, size_of_image, and offsets to sections and debug directories relative to the start of the .DBG file.

    Key constants:

    • IMAGE_SEPARATE_DEBUG_SIGNATURE: 0x4944
    • IMAGE_SEPARATE_DEBUG_FLAGS_MASK: 0x8000
    • IMAGE_SEPARATE_DEBUG_MISMATCH: 0x8000 (indicates the old checksum did not match when the DBG was updated).
  9. How to construct and manipulate SymbolType

    main

    A SymbolType is a packed 16-bit value containing both a SymbolBaseType and a SymbolDerivedType.

    • Use SymbolType::new(base, derived) to construct a type.
    • Use .base_type() to extract the base component (e.g., IMAGE_SYM_TYPE_INT).
    • Use .derived_type() to extract the derived component (e.g., IMAGE_SYM_DTYPE_POINTER).

    Base Types (SymbolBaseType):

    • IMAGE_SYM_TYPE_VOID (0x0001)
    • IMAGE_SYM_TYPE_CHAR (0x0002)
    • IMAGE_SYM_TYPE_SHORT (0x0003)
    • IMAGE_SYM_TYPE_INT (0x0004)
    • IMAGE_SYM_TYPE_LONG (0x0005)
    • IMAGE_SYM_TYPE_STRUCT (0x0008)

    Derived Types (SymbolDerivedType):

    • IMAGE_SYM_DTYPE_POINTER (1)
    • IMAGE_SYM_DTYPE_FUNCTION (2)
    • IMAGE_SYM_DTYPE_ARRAY (3)
    // Constructing a pointer to an integer
    let sym_type = SymbolType::new(SymbolBaseType(IMAGE_SYM_TYPE_INT), SymbolDerivedType(IMAGE_SYM_DTYPE_POINTER));
    
    assert_eq!(sym_type.base_type(), SymbolBaseType(IMAGE_SYM_TYPE_INT));
    assert_eq!(sym_type.derived_type(), SymbolDerivedType(IMAGE_SYM_DTYPE_POINTER));
  10. How the ReadRef trait works for data access

    main

    The ReadRef<'a> trait provides a unified interface for reading data from different sources without necessarily copying it. This is designed to support two primary use cases:

    1. In-memory data: Using &[u8] (e.g., from a memory-mapped file) to access data via references, which is highly efficient for both I/O and memory usage.
    2. On-demand storage access: Using &ReadCache (for environments like WebAssembly where memory mapping might not be available) to read only the specific portions of a file required for parsing.

    Methods in ReadRef accept self by value because the implementor is expected to behave like a reference (e.g., a &[u8] or a wrapper around a reference). Parsers typically use *_at methods to read at specific offsets or use the methods that accept a &mut u64 offset to treat the data block as a stream.

    All methods return a Result<&'a T>, where an error (represented by ()) is returned if the offset or size is out of bounds.

  11. Use the unified write API with `write::Object`

    main

    The write module provides a unified API for creating and modifying relocatable object files via the write::Object trait.

    Note: The unified write API currently only supports writing relocatable object files; it does not support writing executable files. For writing raw structs directly, use the low-level helpers found in the write::modules submodules.

  12. Use the unified read API with `read::Object` and `read::File`

    main

    The object crate provides a unified interface for reading various object file formats (ELF, Mach-O, PE/COFF, XCOFF, and archives) through the read::Object trait.

    To read any file format generically, use the read::File implementation of the read::Object trait. This allows you to interact with different file formats using a single set of methods without needing to manually handle format-specific logic. For more granular control or to access format-specific details not exposed by the unified API, you can use the low-level helpers in the read::modules submodules that operate directly on raw structs.