gigahorse-toolchain

repository·master·Indexed 18 days ago

https://github.com/nevillegrech/gigahorse-toolchain

A binary lifter and toolchain (version 0.1.0) that transforms low-level EVM bytecode into a high-level, function-based three-address representation similar to LLVM IR. Designed for deep static analysis and formal verification of smart contracts, it includes components for memory modeling, storage modeling of nested data structures, and a decompiler for reconstructing functions from lifted IR.

Tokens
13K
Snippets
31
Records
42
Agent score
61%

What's inside gigahorse-toolchain

  1. Understand the memory modeling file layout

    master

    The memory modeling component is organized into several specialized files. Understanding this layout helps in locating specific logic for customization or debugging:

    • memory_modeling.dl: Main entry point for client analyses.
    • memory_modeling_api.dl: Defines the public API.
    • clienthelpers.dl: Common relations for client reuse.
    • memory_addresses.dl: Models free-memory-pointer values and address aliasing.
    • core.dl: Core logic mapping memory reads to writes (outputs MemWriteToMemConsStmtResolved).
    • uses_defs_abstractions.dl: High-level use/def abstractions based on core.dl.
    • arrays.dl: Rules for high-level memory arrays.
    • loops.dl: Modeling of memory copying loops.
    • components.dl: Common components (e.g., ReachableByPassing).
    • helpers.dl: Syntactic patterns and shared relations.
    • misc.dl: Low-level relations.
    • metrics.dl: Evaluation metrics for memory modeling.
  2. Understand the `StorageConstruct` type for modeling nested data

    master

    The StorageConstruct type is an Algebraic Data Type (ADT) used to model arbitrarily nested Ethereum storage structures. It allows you to represent everything from simple constants to complex, nested mappings and arrays.

    Constructors:

    • Constant {value: Value}: A fixed value.
    • StaticArray {parConstruct: StorageConstruct, arraySize: number}: A fixed-size array.
    • Array {parConstruct: StorageConstruct}: A dynamic array.
    • Mapping {parConstruct: StorageConstruct}: A mapping structure.
    • Offset {parConstruct: StorageConstruct, offset: number}: A construct with a specific byte offset.
    • Variable {construct: StorageConstruct}: A wrapper for a storage variable.
    • TightlyPackedVariable {construct: StorageConstruct, byteLow: number, byteHigh: number}: A variable that occupies a specific byte range within a slot.

    Example Mapping: If you have a nested mapping mapping (address => mapping(uint256 => vals)) public complex at slot 0x4, the constructs would look like:

    • The base mapping: Variable(Mapping(Constant(0x4)))
    • An offset within that mapping: Variable(Offset(Mapping(Mapping(Constant(0x4))), 1))
    // Example Solidity structure
    contract StorageExample {
      uint256 public supply;  // slot 0x0
      address public owner;   // slot 0x1
      bool public isPaused;   // slot 0x1
      uint256[] public supplies; // slot 0x2
      mapping (address => bool) public admins; // slot 0x3
      struct vals {uint256 field0; uint256 field1;}
      mapping (address => mapping(uint256 => vals)) public complex; // slot 0x4
    }
    
    // Corresponding Gigahorse constructs
    // Variable(Constant(0x0)) // uint256 supply
    // Variable(Constant(0x1)) // address owner, bool isPaused
    // Variable(Array(Constant(0x2))) // uint256 [] supplies
    // Variable(Mapping(Constant(0x3))) // mapping admins
    // Variable(Mapping(Mapping(Constant(0x4)))) // complex mapping base
    // Variable(Offset(Mapping(Mapping(Constant(0x4))), 1)) // field in complex mapping
  3. Write custom client analyses

    master

    Client analyses can be written in any language by reading the relational files produced by the decompilation step (main.dl).

    Datalog Clients

    Gigahorse provides preferential treatment for clients written in Datalog. You can use the provided clientlib for advanced features like:

    • Customizable dataflow analysis (clientlib/flows.dl)
    • Memory modeling (clientlib/memory_modeling/README.md)
    • Data structure reconstruction (clientlib/storage_modeling/storage_modeling.dl)

    Datalog Template

    To create a client analysis, start with a Souffle datalog file that includes the decompiler imports:

    #include "clientlib/decompiler_imports.dl"
    
    .output ...
    #include "clientlib/decompiler_imports.dl"
    
    .output ...
  4. Understand the structure of the Gigahorse decompiler

    master

    The Gigahorse decompiler is organized into several Souffle logic files that handle different stages of the decompilation and analysis process. Understanding these files helps in identifying where specific logic (like function reconstruction or output generation) resides:

    • local.dl: Contains analyses performed at the per-basic block level.
    • decompiler.dl: The main entry point for the decompiler.
    • functions.dl: Contains the logic for reconstructing functions from the lifted IR.
    • decompiler_output.dl: Handles the logic for generating the three-address code output.
    • decompiler_analytics.dl: Contains logic for computing various analytics.
    • context-sensitivity/*.dl: Contains various context-sensitivity modules that can be plugged into the decompiler. The default context used is the transactional context.
  5. Manually execute the Gigahorse pipeline for development

    master

    For development purposes (e.g., visualizing the CFG of the lifted IR), you can manually run the three stages of the pipeline: Fact generation, Souffle execution, and Visualization.

    Prerequisites: You must set LD_LIBRARY_PATH and LIBRARY_PATH to the directory containing libfunctors.so (typically within the souffle-addon directory).

    Pipeline Steps:

    1. Fact Generation: Translates EVM bytecode into relational format.
    2. Souffle Execution: Runs the main decompilation step (the Datalog program).
    3. Visualization: Processes the relational output.

    It is suggested to add these paths to your .bashrc file.

    # Setup environment
    cd souffle-addon
    export LD_LIBRARY_PATH=`pwd` 
    export LIBRARY_PATH=`pwd` 
    
    # 1. Fact generation
    ./generatefacts <contract> facts
    
    # 2. Run main.dl using Souffle
    souffle -F facts logic/main.dl
    
    # 3. Visualize results
    clients/visualizeout.py
  6. Run the Gigahorse toolchain

    master

    The gigahorse.py script performs binary lifting from EVM code to a high-level three-address representation. It can process individual .hex files or entire directories of contracts.

    Basic Usage

    Run on a single contract:

    ./gigahorse.py examples/long_running.hex

    Run on a directory of contracts (bulk analysis):

    ./gigahorse.py <directory_of_hex_files>

    Pipeline Behavior

    1. Decompilation: Attempts a shrinking context-sensitivity configuration first. If it times out, it falls back to a scalable-fallback (finite-precise) configuration.
    2. Inlining: Performs rounds of inlining small functions to assist client libraries.

    Configuration Flags

    • --disable_scalable_fallback: Disables the second attempt with the scalable-fallback configuration.
    • --disable_inline: Disables the function inlining functionality.
    • -j <number of jobs>: Specifies the number of parallel jobs.
    • -C <client_path>: Specifies a client analysis to run after lifting. Clients can be a comma-separated list of path-reachable or fully-qualified filenames.

    Output

    • Decompilation results: Stored in the .temp directory.
    • Execution metadata: Stored in results.json. This file contains a list of triples [filename, properties, flags].
      • properties: A list of detected issues (non-empty output relations in datalog files).
      • flags: Auxiliary info like "ERROR" or "TIMEOUT".
    ./gigahorse.py examples/long_running.hex
  7. Integrate Ethereum Memory Modeling into client analyses

    master

    To use the memory modeling capabilities in your client analysis, you only need to include the memory_modeling.dl file. This file acts as the primary entry point and includes all necessary libraries and memory modeling components.

    Integration Step: Include memory_modeling.dl in your Souffle analysis files.

    // In your client analysis .dl file
    .include "memory_modeling.dl"
  8. Install Gigahorse via Docker

    master

    For a pre-built environment, use the provided Docker installation scripts.

    For amd64:

    curl -s -L https://raw.githubusercontent.com/nevillegrech/gigahorse-toolchain/master/scripts/docker/install/install_amd64 | bash

    For arm64/m1 (not actively tested):

    curl -s -L https://raw.githubusercontent.com/nevillegrech/gigahorse-toolchain/master/scripts/docker/install/install_arm64 | bash

    After running the script, refresh your shell environment:

    source ~/.bashrc

    Verify the installation with:

    gigahorse --help
    curl -s -L https://raw.githubusercontent.com/nevillegrech/gigahorse-toolchain/master/scripts/docker/install/install_amd64 | bash
  9. Install Gigahorse via local clone (requires Souffle)

    master

    To install Gigahorse from a local clone, you must first ensure the repository is cloned with submodules.

    1. Clone the repository

    git clone git@github.com:nevillegrech/gigahorse-toolchain.git --recursive

    If you already cloned without --recursive, run git submodule update --init --recursive to fetch the souffle-addon submodule.

    2. Install System Dependencies (Debian-based)

    You need the following installed on your system:

    • Boost libraries: apt install libboost-all-dev
    • Z3: apt install libz3-dev
    • Souffle: Version 2.3 or 2.4.1 (recommended to use the 2.4.1 release).
    • uv: The Astral Python project manager. Install via curl -LsSf https://astral.sh/uv/install.sh | sh.

    3. Build Souffle Addon

    Navigate to the souffle-addon directory and build the custom functors:

    cd souffle-addon && make WORD_SIZE=$(souffle --version | sed -n 3p | cut -c12,13)

    4. Set up Python Environment

    Use uv to provision CPython 3.13 and project dependencies into a local .venv:

    uv sync

    Optional Extras:

    • For Datalog directive parsing and interactive graph visualization: uv sync --extra tooling
    • For Graphviz/dot rendering: uv sync --extra viz
    git clone git@github.com:nevillegrech/gigahorse-toolchain.git --recursive
  10. How the Fact Generation system works

    master

    Gigahorse uses a tiered system to generate facts (intermediate representations) from Ethereum smart contracts. The process is managed by a MixedFactGenerator which selects the appropriate strategy based on the input filename pattern.

    There are three main types of generators:

    1. DecompilerFactGenerator: The primary method. It uses a multi-stage fallback mechanism to handle complex contracts. It starts with a precise decompiler and, if it fails or times out, falls back to ScalableDecomp and finally LastResortDecomp by reducing the MaxContextDepth.csv value.
    2. ContractStitchingGenerator: Used for multi-contract manifests. It takes a JSON manifest containing multiple contract addresses and IDs, extracts their individual facts, prefixes identifiers to prevent collisions, and merges them into a single set of relations.
    3. CustomFactGenerator: Allows running user-defined scripts or Datalog files. It supports both .dl files (run via Souffle) and other scripts (run as subprocesses with -i input and -o output flags).

    Generators are assigned a priority (FACT_GEN_HIGH_PRIORITY or FACT_GEN_LOW_PRIORITY) to determine the order of execution.

  11. Tune context sensitivity algorithms

    master

    Gigahorse supports several context sensitivity algorithms to control the precision and depth of the decompilation. You can select an algorithm using the -M flag with the CONTEXT_SENSITIVITY key.

    Available Algorithms:

    • TransactionalWithShrinkingContext: The default. A composite algorithm that includes a public entry point component and a variable depth selective context component that can shrink to drop elements that no longer provide precision.
    • TransactionalContext: The algorithm from the Elipmoc paper. A composite algorithm with a public entry point and variable depth selective context.
    • CallSiteContext: Call-site (or block-site) context sensitivity with variable depth.
    • CallSiteContextPlus: Call-site context sensitivity including a public function component.
    • SelectiveContext: Selective call-site context including only dynamic jumps.
    • SelectiveContextPlus: Selective context including a public function component.

    You can also control the maximum context depth using the -cd flag.

    Note: Changing the algorithm via the default 2-step pipeline is not recommended as it may override defaults for both configurations.

    python3.8 gigahorse.py examples/long_running.hex --disable_scalable_fallback -M "CONTEXT_SENSITIVITY=CallSiteContext"
  12. View the textual representation of lifted IR

    master

    To obtain a pretty-printed textual representation of the lifted Intermediate Representation (IR), use the clients/visualizeout.py client.

    The output is a file named contract.tac located in the out/ folder for each analyzed contract.

    Example Command:

    ./gigahorse.py -C clients/visualizeout.py examples/long_running.hex

    Output Location: For the example above, the output is at .temp/long_running/out/contract.tac.

    Example IR Block Format:

        Begin block 0x3e
        prev=[0xb], succ=[0x10ee, 0x49]
        =================================
        0x3f: v3f(0xf42fdfb) = CONST 
        0x44: v44 = EQ v3f(0xf42fdfb), v32
        0x10c7: v10c7(0x10ee) = CONST 
        0x10c8: JUMPI v10c7(0x10ee), v44

    Note: Pretty-printed variable identifiers do not correspond to the identifiers used in the underlying datalog facts.

    ./gigahorse.py -C clients/visualizeout.py examples/long_running.hex