go-macho

repository·master·Indexed 42 days ago

https://github.com/blacktop/go-macho

A Go package for advanced manipulation, creation, and inspection of Mach-O object files. It extends the capabilities of the standard library's debug/macho, providing support for all load commands and architectures, custom Mach-O file creation, and parsing of Swift and Objective-C runtime information. It includes tools for reading/writing code signatures, fixup chains, and detailed symbol table inspection.

Tokens
17.2K
Snippets
98
Records
142
Agent score
54%

What's inside go-macho

  1. What is go-macho and when to use it

    master

    The go-macho package provides advanced access to and creation of Mach-O object files. It is a more feature-rich alternative to Go's standard debug/macho library.

    Use go-macho if you need to:

    • Cover ALL load commands and architectures.
    • Generate nice summary string outputs for Mach-O files.
    • Create custom Mach-O files.
    • Parse Objective-C runtime information (using the non-fragile runtime ABI; note that legacy __OBJC metadata is detected but reported as unsupported).
    • Parse Swift runtime information.
    • Read or write code signature information.
    • Parse fixup chain information.
  2. Open and inspect a Mach-O file

    master

    You can use macho.Open to load a Mach-O object file. Once opened, you can access various metadata, such as the File TOC (Table of Contents), which provides a summary string representation of the file structure.

    package main
    
    import (
        "fmt"
        "github.com/blacktop/go-macho"
    )
    
    func main() {
        m, err := macho.Open("/path/to/macho")
        if err != nil {
            panic(err)
        }
        defer m.Close()
    
        fmt.Println(m.FileTOC.String())
    }
  3. Enable go-macho diagnostic logging

    master

    The library uses Go's structured logging (log/slog) for diagnostics, such as warnings about malformed Mach-O data. By default, no logs are emitted. To enable them, configure a default slog handler in your application. Setting the level to slog.LevelWarn or slog.LevelDebug will allow you to see library diagnostics.

    import (
    	"log/slog"
    	"os"
    )
    
    // Enable debug logging
    slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
    	Level: slog.LevelWarn, // Or slog.LevelDebug for more verbose output
    })))
  4. Inspect Objective-C metadata in MachO files

    master

    The macho package provides high-level APIs to parse and inspect Objective-C runtime metadata within MachO binaries. You can detect if a file contains Objective-C metadata, retrieve a table of contents for all Objective-C objects, and extract specific information about classes, categories, and protocols.

    Key capabilities include:

    • Detecting Objective-C presence via HasObjC().
    • Retrieving a summary of object counts via GetObjCToc().
    • Extracting all classes via GetObjCClasses().
    • Extracting all categories via GetObjCCategories().
    • Accessing specific class information at a virtual memory address using GetObjCClass(vmaddr).
  5. Optimize LC_DYLD_INFO and LC_DYLD_INFO_ONLY load commands

    master

    The export process includes an optimization step for LC_DYLD_INFO and LC_DYLD_INFO_ONLY load commands. It relocates the following data blocks to the end of the linkedit buffer to optimize layout:

    • RebaseSize (Rebase data)
    • BindSize (Bind data)
    • WeakBindSize (Weak bind data)
    • LazyBindSize (Lazy bind data)
    • ExportSize (Export data)

    After relocation, the offsets in the load command are updated to point to the new positions in the linkedit buffer, and the data is padded to ensure pointer alignment based on the file's pointer size.

  6. Understand the FatFile and FatArch structures

    master

    A FatFile represents a Mach-O universal binary. It contains a FatHeader and a slice of FatArch objects.

    • FatFile: The top-level container. It embeds FatHeader and manages the lifecycle of the underlying reader via a closer.
    • FatHeader: Contains the Magic number (must be types.MagicFat), the Count of architectures, and the Arches slice.
    • FatArch: Represents a single architecture within the fat file. It embeds a FatArchHeader and provides a pointer to a *File (the parsed Mach-O image for that specific architecture) and the raw data bytes.
    • FatArchHeader: Contains the metadata for a specific architecture, including CPU, SubCPU, Offset (position in the fat file), Size, and Align.
  7. How indirect symbol tables are adjusted during export

    master

    When new local symbols are prepended to the symbol table during an export operation, the indices in the indirect symbol table (Dysymtab.IndirectSyms) must be shifted to maintain correctness.

    For every indirect symbol that is not a sentinel value (specifically, it must not have the types.INDIRECT_SYMBOL_LOCAL or types.INDIRECT_SYMBOL_ABS flags set), its index is incremented by the number of new local symbols added: indSym + uint32(len(locals)).

  8. How DYSYMTAB symbol ordering works

    master

    When reconstructing or exporting symbol tables, go-macho follows the standard Mach-O convention for DYSYMTAB ordering. Symbols must be categorized and written in the following sequence:

    1. Locals: Symbols that are not external (e.g., local function symbols).
    2. External Defined: Symbols that are external and defined within the binary (including re-exports from LC_DYLD_EXPORTS_TRIE).
    3. Undefined: Symbols that are external but undefined (imports).

    During the export process, the library collects these into localSyms, extdefSyms, and undefSyms slices to ensure the resulting Nlist entries and the string pool are written in this specific order. Re-export entries are encoded in the string pool using the format name\x00reexport.

  9. Understand the Load interface

    master

    In go-macho, all Mach-O load commands implement the Load interface. This abstraction allows the library to treat various command types (segments, symbol tables, dylibs, etc.) uniformly when traversing a Mach-O file.

    Key methods include:

    • Command(): Returns the types.LoadCmd identifier.
    • LoadSize(): Returns the total size of the command in bytes.
    • Raw(): Returns the underlying byte slice.
    • Write(buf *bytes.Buffer, o binary.ByteOrder): Serializes the command into a buffer.
    • MarshalJSON(): Provides a JSON representation of the command.
  10. Configure Mach-O file loading with FileConfig

    master

    When calling NewFile, you can provide a FileConfig to customize how the Mach-O file is parsed and read. This allows you to include or exclude specific load commands, provide custom readers for sections or caches, and specify a custom VMAddrConverter.

    Key fields in FileConfig:

    • Offset: The starting offset in the reader.
    • LoadIncluding: A slice of types.LoadCmd to explicitly include.
    • LoadExcluding: A slice of types.LoadCmd to explicitly exclude.
    • SectionReader: A custom types.MachoReader for section access.
    • CacheReader: A custom types.MachoReader for cache access.
    • VMAddrConverter: A custom types.VMAddrConverter.
    • RelativeSelectorBase: The base VM address for relative selectors.
  11. Write load commands to a buffer

    master

    The writeLoadCommands method iterates through the file's load commands and writes them to a provided *bytes.Buffer.

    • For LC_SEGMENT or LC_SEGMENT_64, it writes the segment header and then sequentially writes the data for every section within that segment.
    • For all other command types, it calls the command's own Write method.

    This ensures that the segment data and the command structures are serialized in the correct order for the Mach-O file format.

    func (f *File) writeLoadCommands(buf *bytes.Buffer) error