All incidents

DeRace vesting proxy ownership takeover and emergency exit

Share
Sep 03, 2021 21:58 UTCAttackLoss: 5,760,000 DERCManually checked2 exploit txWindow: 5m 58s
Estimated Impact
5,760,000 DERC
Label
Attack
Exploit Tx
2
Addresses
1
Attack Window
5m 58s
Sep 03, 2021 21:58 UTC → Sep 03, 2021 22:04 UTC

Exploit Transactions

TX 1Ethereum
0xd5e2edd6089dcf5dca78c0ccbdf659acedab173a8ab3cb65720e35b640c0af7c
Sep 03, 2021 21:58 UTCExplorer
TX 2Ethereum
0x96bf6bd14a81cf19939c0b966389daed778c3a9528a6c5dd7a4d980dec966388
Sep 03, 2021 22:04 UTCExplorer

Victim Addresses

0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940Ethereum

Loss Breakdown

5,760,000DERC

Similar Incidents

Root Cause Analysis

DeRace vesting proxy ownership takeover and emergency exit

1. Incident Overview TL;DR

An unprivileged Ethereum EOA 0x2708cace7b42302af26f1ab896111d87faeff92f seized ownership of a funded DeRace vesting proxy 0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940 and drained its entire DERC balance. The attacker first called an unprotected init(...) function on the shared vesting implementation 0xf17ca0e0f24a5fa27944275fa0cedec24fbf8ee2 via the proxy in tx 0xd5e2edd6089dcf5dca78c0ccbdf659acedab173a8ab3cb65720e35b640c0af7c (block 13155321), reinitializing an already-configured proxy and overwriting the owner and vesting schedule. With ownership reassigned, the attacker then called emergencyExit(address) via the same proxy in tx 0x96bf6bd14a81cf19939c0b966389daed778c3a9528a6c5dd7a4d980dec966388 (block 13155350), transferring exactly 5,760,000 DERC from the proxy to the attacker EOA in a single ERC20 transfer.

This is an Anyone-Can-Take (ACT) opportunity: any unprivileged EOA with sufficient ETH for gas, using only public ABI/bytecode information and canonical on-chain data, can reproduce the same two-transaction strategy against any similarly configured proxy that delegates to implementation 0xf17c.... The profit predicate is a strictly positive net gain of 5,760,000 DERC with zero DERC-denominated fees; gas fees are paid only in ETH and sum to 0.0399448 ETH across the two attacker-crafted transactions.

2. Key Background

The DeRace (DERC) token 0x9fa69536d1cda4a04cfb50688294de75b505a9ae is a standard Ownable, Pausable ERC20 with 18 decimals and a fixed total supply of 120,000,000 * 1e18 minted to the deployer. The verified source at artifacts/root_cause/seed/1/0x9fa6.../src/Contract.sol shows that balances are stored in a single mapping slot and that transfers follow the usual ERC20 semantics:

// DERC ERC20 excerpt (verified source)
contract ERC20 is Pausable {
    mapping(address => uint256) private _balances;
    uint256 private _totalSupply = 120e6 ether;

    event Transfer(address indexed from, address indexed to, uint256 value);

    constructor() {
        _balances[_msgSender()] = _totalSupply;
    }

    function balanceOf(address account) public view returns (uint256) {
        return _balances[account];
    }
}

On top of DERC, the victim system deploys vesting contracts as EIP‑1167 minimal proxies that delegate all logic to a shared implementation at 0xf17ca0e0f24a5fa27944275fa0cedec24fbf8ee2. The proxy involved in this incident is 0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940; its runtime code in the trace prestate is a standard minimal proxy that forwards to 0xf17c..., and its storage holds the vesting parameters and owner field.

The vesting implementation is not verified on Etherscan, but Heimdall decompilation and traces show that it exposes:

  • an init(uint256 start, uint256[] releasePeriods, uint256[] releasePercents, address token) function that writes owner and vesting configuration into storage, and
  • an emergencyExit(address token) function that transfers the proxy’s entire token balance to a caller-specified recipient, gated only by ownership.

Proxy 0x2fd6... was first initialized and funded as intended:

  • Deployer / factory 0x95626473b6782292d20f1b07a85a8b7f6ab63677 created and initialized the proxy, becoming the initial owner.
  • Funder 0x45166749c271f0688f624f6c1e897ad14b8bf6d7 transferred 5,760,000 DERC to the proxy in tx 0x5f8351ee555d6111161fd4a8d8232ebc665e8d05d8e24fe54c9afaac4df54b44 (block 12951637), as shown by the Etherscan token transfer API:
// Etherscan tokentx for DERC vs proxy 0x2fd6... (excerpt)
{
  "hash": "0x5f8351ee555d6111161fd4a8d8232ebc665e8d05d8e24fe54c9afaac4df54b44",
  "from": "0x45166749c271f0688f624f6c1e897ad14b8bf6d7",
  "to":   "0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940",
  "value": "5760000000000000000000000",
  "tokenSymbol": "DERC",
  "tokenDecimal": "18"
}

At this point, the proxy correctly held 5,760,000 DERC on behalf of vesting beneficiaries, with ownership and release schedule under the control of 0x9562.... The system’s safety relies on two implicit invariants:

  • only the intended owner or controlled governance can change vesting parameters or trigger full withdrawals, and
  • once a proxy is initialized and funded, arbitrary EOAs cannot reinitialize it or seize ownership.

3. Vulnerability Analysis & Root Cause Summary

The core vulnerability is a reinitialization and access‑control bug in the vesting implementation 0xf17c.... The init(...) function:

  • can be called via delegatecall from any minimal proxy pointing to 0xf17c...,
  • has no access control (no onlyOwner or equivalent), and
  • does not record or check an “initialized” flag or a constraint such as require(owner == address(0)).

As a result, any EOA can call init(...) through a proxy at any time, even after the proxy has already been initialized and funded. When called, init(...) overwrites the owner address and vesting schedule in the proxy’s storage. Once ownership is reassigned, the attacker can immediately invoke emergencyExit(address) to transfer the proxy’s entire DERC balance to an arbitrary address they control.

In the incident at hand, this bug allowed 0x2708... to:

  1. Reinitialize proxy 0x2fd6... in tx 0xd5e2e..., replacing owner 0x9562... with 0x2708... and simplifying the vesting schedule.
  2. Drain the full 5,760,000 DERC balance to 0x2708... in tx 0x96bf6b... via emergencyExit(address).

Because both functions are callable using only standard ABI information and Ethereum transaction semantics, and because no privileged off-chain coordination is required, this forms an ACT opportunity for any unprivileged EOA.

4. Detailed Root Cause Analysis

4.1 Pre‑state and funding

We define the pre‑state σ_B as Ethereum mainnet state immediately before tx 0xd5e2e... in block 13155321, reconstructed from QuickNode debug_traceTransaction (prestateTracer) and Etherscan metadata and source/decompilation for the relevant contracts.

At this point:

  • Proxy 0x2fd6... has already been initialized once by 0x9562... with a multi‑period vesting schedule, reflected in several storage slots encoding release timestamps and percentages.
  • Token transfer logs show that 0x2fd6... holds exactly 5,760,000 DERC, funded by 0x4516... in tx 0x5f83... as shown above.
  • Ownership logs (OwnershipTransferred events) for 0x2fd6... show an earlier transfer from the deployer to 0x9562..., but no involvement of 0x2708... until tx 0xd5e2e....

4.2 Ownership takeover via unguarded init(...)

Tx 0xd5e2edd6089dcf5dca78c0ccbdf659acedab173a8ab3cb65720e35b640c0af7c is sent by attacker EOA 0x2708... to proxy 0x2fd6... with calldata encoding init(uint256,uint256[],uint256[],address). The QuickNode prestateTracer diff for this tx shows storage for 0x2fd6... changing as follows:

// debug_traceTransaction prestateTracer for tx 0xd5e2e... (excerpt)
{
  "post": {
    "0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940": {
      "storage": {
        "0x...0000": "0x00000000000000000000002708cace7b42302af26f1ab896111d87faeff92f00",
        "0x...0001": "0x0000000000000000000000000000000000000000000000000000000061cf6f51",
        "0x...0002": "0x0000000000000000000000000000000000000000000000000000000062267251",
        "0x...0003": "0x...0001",
        "0x...0004": "0x...0001",
        "0x8a35acfb...9b": "0x...2710"
      }
    }
  },
  "pre": {
    "0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940": {
      "storage": {
        "0x...0000": "0x000000000000000000000095626473b6782292d20f1b07a85a8b7f6ab6367700",
        "0x...0001": "0x...61093038",
        "0x...0002": "0x...62e3cc38",
        "0x...0003": "0x...0004",
        "0x...0004": "0x...0004",
        "0x8a35acfb...9b": "0x...09c4",
        "0x8a35acfb...9c": "0x...09c4",
        "0x8a35acfb...9d": "0x...09c4",
        "0x8a35acfb...9e": "0x...09c4"
      }
    }
  }
}

Interpretation:

  • Storage slot 0x...0000 encodes the owner address. Its value changes from a word embedding 0x95626473b6782292d20f1b07a85a8b7f6ab63677 (the deployer/initial owner) to one embedding 0x2708cace7b42302af26f1ab896111d87faeff92f.
  • Slots 0x...0001 and 0x...0002 change from earlier vesting start/end timestamps to new ones.
  • Slots keyed by 0x8a35acfb... change from multiple entries with value 0x09c4 (decimal 2500, representing 25% each over four periods) to a single 0x2710 entry (decimal 10000, representing 100% in a single period).

Ownership transfer logs for 0x2fd6... in ownership_transferred_logs.json show a matching OwnershipTransferred event in block 13155321 with:

  • previousOwner = 0x95626473b6782292d20f1b07a85a8b7f6ab63677
  • newOwner = 0x2708cace7b42302af26f1ab896111d87faeff92f

Combined with the Heimdall decompilation of implementation 0xf17c..., which shows init(...) writing owner and vesting parameters without checking an initialized flag or restricting the caller, this establishes that:

  • init(...) is callable by arbitrary EOAs through the proxy,
  • it can be invoked more than once, and
  • in this tx it reassigns ownership and vesting configuration of an already funded proxy to the attacker.

4.3 Full token drain via emergencyExit(address)

After seizing ownership, 0x2708... sends tx 0x96bf6bd14a81cf19939c0b966389daed778c3a9528a6c5dd7a4d980dec966388 to proxy 0x2fd6... at block 13155350. Etherscan’s transaction metadata (stored under seed artifacts) shows:

  • from = 0x2708cace7b42302af26f1ab896111d87faeff92f
  • to = 0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940
  • input = 0xa441d0670000000000000000000000002708cace7b42302af26f1ab896111d87faeff92f

Selector 0xa441d067 corresponds to emergencyExit(address), and the argument is the attacker EOA. The QuickNode receipt and decoded logs for this tx confirm the token transfer:

// erc20_transfers_decoded.json for tx 0x96bf6b... (excerpt)
[
  {
    "token": "0x9fa69536d1cda4a04cfb50688294de75b505a9ae",
    "from":  "0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940",
    "to":    "0x2708cace7b42302af26f1ab896111d87faeff92f",
    "amount": "5760000000000000000000000"
  }
]

The balance-diff tracer for the same tx quantifies the ERC20 deltas:

// balance_diff_prestate.json for tx 0x96bf6b... (excerpt)
{
  "erc20_balance_deltas": [
    {
      "token":  "0x9fa69536d1cda4a04cfb50688294de75b505a9ae",
      "holder": "0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940",
      "before": "5760000000000000000000000",
      "after":  "0",
      "delta":  "-5760000000000000000000000"
    },
    {
      "token":  "0x9fa69536d1cda4a04cfb50688294de75b505a9ae",
      "holder": "0x2708cace7b42302af26f1ab896111d87faeff92f",
      "before": "0",
      "after":  "5760000000000000000000000",
      "delta":  "5760000000000000000000000"
    }
  ]
}

An eth_call against the DERC contract at block 13155350, saved in derc_balance_at_13155350.json, returns a zero balance for 0x2fd6..., consistent with the tracer output. Together, these artifacts show that:

  • immediately before tx 0x96bf6b..., the proxy holds exactly 5,760,000 DERC, and
  • after the tx, the proxy’s DERC balance is zero and the attacker’s DERC balance has increased by the same amount.

4.4 Invariant and breakpoint

The relevant safety invariant for any funded vesting proxy that has already been initialized is:

  • No unprivileged EOA other than the intended owner may seize ownership or unilaterally redirect, accelerate, or fully withdraw vested tokens; in particular, init(...) must not be callable again by arbitrary callers after the initial setup, and ownership must remain under controlled governance or the designated owner.

The concrete breakpoint that violates this invariant is the design of init(...) in implementation 0xf17c...:

  • init(...) is callable at any time via delegatecall from proxies.
  • It does not enforce onlyOwner or an equivalent modifier.
  • It does not check or record an initialized state (for example, require(owner == address(0))).

Transaction 0xd5e2e... is the first time 0x2708... calls init(...) through proxy 0x2fd6.... Because the function is completely unguarded, the call succeeds under EVM rules and overwrites owner and vesting schedule in a single step, breaking the invariant. The subsequent call to emergencyExit(address) in 0x96bf6b... realizes the impact of this broken invariant by withdrawing all funds.

5. Adversary Flow Analysis

The adversary’s strategy is a two‑step ACT flow on Ethereum mainnet that any unprivileged EOA can reproduce using only public on-chain data and standard tooling.

5.1 Transaction 1 (ownership takeover)

  • Tx hash: 0xd5e2edd6089dcf5dca78c0ccbdf659acedab173a8ab3cb65720e35b640c0af7c
  • Block: 13155321
  • From: 0x2708cace7b42302af26f1ab896111d87faeff92f (attacker EOA)
  • To: 0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940 (vesting proxy)
  • Calldata: init(uint256,uint256[],uint256[],address) with chosen schedule and token address.
  • On-chain effect: Proxy 0x2fd6... delegatecalls into implementation 0xf17c...; init(...) overwrites owner from 0x9562... to 0x2708... and replaces the original multi‑period vesting schedule with a single 100% release period. Token balances remain unchanged.
  • Gas cost: Receipt data (gasUsed = 0x12e5e, effectiveGasPrice = 0x2e90edd000) imply a deterministic ETH fee of 0.0154812 ETH.

This transaction is realizable by any unprivileged EOA who:

  • knows the proxy address 0x2fd6... and target implementation ABI (recoverable from decompile and public metadata), and
  • can construct the init(...) calldata and pay the gas fee.

5.2 Transaction 2 (emergency exit drain)

  • Tx hash: 0x96bf6bd14a81cf19939c0b966389daed778c3a9528a6c5dd7a4d980dec966388
  • Block: 13155350
  • From: 0x2708cace7b42302af26f1ab896111d87faeff92f
  • To: 0x2fd6...
  • Calldata: emergencyExit(address) with recipient set to 0x2708....
  • On-chain effect: Proxy 0x2fd6... delegatecalls into 0xf17c...; emergencyExit(address) reads the proxy’s entire DERC balance and transfers 5,760,000 DERC from 0x2fd6... to 0x2708.... No other ERC20 movements occur.
  • Gas cost: Receipt data (gasUsed = 0xeee7, effectiveGasPrice = 0x5d21dba000) imply a deterministic ETH fee of 0.0244636 ETH.

The attacker’s net portfolio change in the chosen reference asset (DERC) is:

  • Fees paid in DERC: 0
  • DERC gained: +5,760,000 DERC
  • Net DERC change: +5,760,000 DERC

The ETH gas outlay (0.0154812 + 0.0244636 = 0.0399448 ETH) is fully determined by the receipts but is not used as the reference asset; the ACT profit predicate is satisfied purely in DERC units.

5.3 Reusability of the strategy

Analysis of 0x2708...’s broader transaction history shows the same pattern applied to another minimal proxy 0xdd571023d95ff6ce5716bf112ccb752e86212167 that also delegates to implementation 0xf17c.... Decompilation of 0xdd57... confirms that it follows the same minimal proxy pattern.

Given:

  • the public nature of proxy and implementation code (via bytecode and decompile),
  • the absence of access control and initialization guards in init(...), and
  • the deterministic behavior of emergencyExit(address),

any unprivileged EOA that learns of a funded proxy pointing to 0xf17c... can reproduce this two‑transaction strategy to seize ownership and drain the proxy’s tokens.

6. Impact & Losses

6.1 Quantified loss for this incident

For proxy 0x2fd6...:

  • Token: DERC (0x9fa6...)
  • Amount lost: 5,760,000 DERC (5,760,000 * 1e18 base units)
  • Victim address: 0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940
  • Adversary address: 0x2708cace7b42302af26f1ab896111d87faeff92f

The loss is confirmed by:

  • Etherscan token transfer logs (derc_transfers_tokentx.json) showing the funding tx 0x5f83... (from 0x4516... to 0x2fd6...) and the drain tx 0x96bf6b... (from 0x2fd6... to 0x2708...).
  • Balance-diff tracer output for tx 0x96bf6b... showing the proxy’s DERC balance decreasing from 5,760,000 * 1e18 to 0 and the attacker’s balance increasing by the same amount.
  • A direct eth_call at block 13155350 returning zero DERC balance for proxy 0x2fd6....

6.2 Systemic impact

The DERC ERC20 contract behaves according to its specification; the vulnerability lies entirely in the vesting implementation and proxy configuration:

  • init(...) is callable indefinitely and overwrites owner and schedule without restrictions.
  • emergencyExit(address) allows the owner to withdraw all tokens from a proxy.

Any other vesting proxy that:

  • delegates to implementation 0xf17c...,
  • is initialized and funded, and
  • does not have additional protective controls (such as pausing transfers or revoking the vulnerable implementation),

is vulnerable to the same ACT strategy. The data collection attempt proxies_pointing_to_0xf17c_from_0x9562.json did not identify additional proxies from the same deployer within the scanned block range, but this does not change the mechanism: the root cause is implementation‑level and affects all proxies that share it.

7. References

  • Victim protocol and token

    • DeRace Token (DERC) ERC20 contract: 0x9fa69536d1cda4a04cfb50688294de75b505a9ae
    • Verified source and build artifacts: artifacts/root_cause/seed/1/0x9fa69536d1cda4a04cfb50688294de75b505a9ae/
  • Vesting proxies and implementation

    • Vulnerable vesting implementation: 0xf17ca0e0f24a5fa27944275fa0cedec24fbf8ee2
      • Etherscan metadata and Heimdall decompile: artifacts/root_cause/data_collector/iter_2/contract/1/0xf17ca0e0f24a5fa27944275fa0cedec24fbf8ee2/
    • Main victim proxy: 0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940
      • Etherscan metadata, decompile, ownership logs, tx history, and DERC balance: artifacts/root_cause/data_collector/iter_2/contract/1/0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940/ and artifacts/root_cause/data_collector/iter_3/address/1/0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940/
    • Additional proxy using the same implementation: 0xdd571023d95ff6ce5716bf112ccb752e86212167
      • Etherscan metadata and decompile: artifacts/root_cause/data_collector/iter_3/contract/1/0xdd571023d95ff6ce5716bf112ccb752e86212167/
  • Key transactions

    • Funding tx (DERC → proxy):
      • 0x5f8351ee555d6111161fd4a8d8232ebc665e8d05d8e24fe54c9afaac4df54b44 (block 12951637)
      • Etherscan tokentx API result: artifacts/root_cause/data_collector/iter_3/address/1/0x2fd602ed1f8cb6deaba9bedd560ffe772eb85940/derc_transfers_tokentx.json
    • Ownership takeover via init(...):
      • 0xd5e2edd6089dcf5dca78c0ccbdf659acedab173a8ab3cb65720e35b640c0af7c (block 13155321)
      • QuickNode debug_traceTransaction prestateTracer and receipt: artifacts/root_cause/data_collector/iter_2/tx/1/0xd5e2edd6089dcf5dca78c0ccbdf659acedab173a8ab3cb65720e35b640c0af7c/
    • Emergency exit drain via emergencyExit(address):
      • 0x96bf6bd14a81cf19939c0b966389daed778c3a9528a6c5dd7a4d980dec966388 (block 13155350)
      • Receipt, decoded ERC20 transfers, debug traces, and balance diffs:
        artifacts/root_cause/data_collector/iter_2/tx/1/0x96bf6bd14a81cf19939c0b966389daed778c3a9528a6c5dd7a4d980dec966388/ and
        artifacts/root_cause/data_collector/iter_3/tx/1/0x96bf6bd14a81cf19939c0b966389daed778c3a9528a6c5dd7a4d980dec966388/
  • Adversary account and behavior

    • Adversary EOA: 0x2708cace7b42302af26f1ab896111d87faeff92f
      • Normal and internal tx history: artifacts/root_cause/data_collector/iter_2/address/1/0x2708cace7b42302af26f1ab896111d87faeff92f/

These references together provide a complete, reproducible on-chain evidence trail for the ownership takeover and emergency exit drain, and for the underlying implementation-level vulnerability that makes this an ACT opportunity.