Skip to main content
Sigvex

Oracle Manipulation Exploit Generator

Sigvex exploit generator that validates spot-price oracle manipulation by comparing contract execution under normal vs. artificially imbalanced DEX reserve configurations.

Oracle Manipulation Exploit Generator

Overview

The oracle manipulation exploit generator validates findings from the oracle_manipulation detector by simulating a DEX pool reserve imbalance and checking whether the contract’s behavior changes in response to artificially manipulated pool prices. A contract that reads spot prices directly from a DEX without TWAP protection will behave differently when the reserve ratio is skewed, confirming exploitability.

Oracle manipulation attacks are a class of economic exploit distinct from code bugs — the protocol’s logic is correct, but the price data it trusts can be temporarily falsified within a single transaction using flash loans or large swaps.

Note: Exploit generation in Sigvex is for vulnerability validation purposes only.

Attack Scenario

  1. Setup: The attacker identifies a protocol that computes a critical value (collateral ratio, swap output, liquidation threshold) from a DEX pool’s reserve0 / reserve1 ratio.
  2. Trigger: The attacker performs a large swap (or uses a flash loan) to drain reserve0 from 1_000_000 to 100_000 tokens while inflating reserve1 from 1_000_000 to 10_000_000 — a 10:1 imbalance. The spot price reserve1 / reserve0 shifts from 1.0 to 100.0.
  3. Exploitation: The attacker calls the vulnerable function. The contract queries dex_pool.getReserves() and computes a price based on the manipulated ratio. For a lending protocol, this inflates collateral value; for a DEX aggregator, it produces a favorable swap rate.
  4. Impact: The attacker extracts value (borrows more than collateral supports, receives more tokens than fair value) and then reverses the trade to restore the pool ratio, repaying any flash loan in the same transaction.

Exploit Mechanics

The generator configures two world states for a simulated DEX pool contract:

Normal reserves (Scenario 1):

  • reserve0 (storage slot 8): 1_000_000
  • reserve1 (storage slot 9): 1_000_000
  • Price ratio: 1:1

Manipulated reserves (Scenario 2):

  • reserve0 (storage slot 8): 100_000 (drained to 10%)
  • reserve1 (storage slot 9): 10_000_000 (inflated 10x)
  • Price ratio: 100:1

The calldata targets the getPrice() function (selector 0x98d5fdac) unless a specific selector is available from the finding location.

Verdict logic:

Normal result Manipulated result Finding
Succeeded Reverted Confirmed vulnerable (confidence 0.75): contract rejects manipulated price, meaning it validates — but not in the right direction
Reverted Succeeded Confirmed critical (confidence 0.75): behavior inverted by price manipulation
Both succeeded Same gas ± tolerance Likely vulnerable (confidence 0.60): no apparent slippage protection
Both reverted Inconclusive

The generated PoC illustrates the attack structure:

contract OracleManipulator {
    function attack() external {
        // 1. Flash loan large amount of token0
        // 2. Swap to manipulate pool reserves (drain reserve0, inflate reserve1)
        // 3. Call vulnerable contract that reads manipulated spot price
        // 4. Profit from inflated price
        // 5. Reverse swap to restore pool
        // 6. Repay flash loan
    }
}

Remediation

Replace direct reserve reads with TWAP queries or external aggregated price feed:

// VULNERABLE: Reads instantaneous pool reserves
function getCollateralValue(address token, uint256 amount) external view returns (uint256) {
    (uint112 r0, uint112 r1,) = IAmmV2Pair(pair).getReserves();
    return amount * r1 / r0; // manipulable in a single tx
}

// SECURE: Uses aggregated price feed with staleness check
function getCollateralValue(address token, uint256 amount) external view returns (uint256) {
    (, int256 price,, uint256 updatedAt,) = aggregatorFeed.latestRoundData();
    require(block.timestamp - updatedAt <= MAX_STALENESS, "Stale price");
    return amount * uint256(price) / 1e8;
}