All incidents

Orion redeemAtomic exploit drains BNB and token reserves

Share
May 28, 2024 04:25 UTCAttackLoss: 79.9 BNB, 19,844.69 USDT +11 moreManually checked1 exploit txWindow: Atomic
Estimated Impact
79.9 BNB, 19,844.69 USDT +11 more
Label
Attack
Exploit Tx
1
Addresses
1
Attack Window
Atomic
May 28, 2024 04:25 UTC → May 28, 2024 04:25 UTC

Exploit Transactions

TX 1BSC
0x660837a1640dd9cc0561ab7ff6c85325edebfa17d8b11a3bb94457ba6dcae18c
May 28, 2024 04:25 UTCExplorer

Victim Addresses

0xe9d1d2a27458378dd6c6f0b2c390807aed2217caBSC

Loss Breakdown

79.9BNB
19,844.69USDT
62,444.73XRP
1,741.2LINK
0.303464BTCB
4.81ETH
785.59DOT
498,921.93ORN
112,610.79DOGE
86,361.31COTI
11,001.11USDC
237.88EGLD
198.36AVAX

Similar Incidents

Root Cause Analysis

Orion redeemAtomic exploit drains BNB and token reserves

1. Incident Overview TL;DR

On BNB Chain block 39107494, an unprivileged adversary cluster exploited Orion Exchange's BNB Chain deployment (AdminUpgradeabilityProxy at 0xe9d1d2a27458378dd6c6f0b2c390807aed2217ca) to drain BNB and multiple ERC20 tokens in a single transaction 0x660837a1640dd9cc0561ab7ff6c85325edebfa17d8b11a3bb94457ba6dcae18c. The attacker used an external orchestrator contract to obtain a USDT flash swap, deposit into Orion, fabricate large internal balances via redeemAtomic (delegating to LibAtomic.doRedeemAtomic), and then withdraw real assets via withdrawTo while remaining superficially margin-positive.

The root cause is that Orion's redeemAtomic flow allows signed RedeemOrders to credit internal balances without binding them to concrete atomicSwaps lock entries or real reserve-backed positions. As long as MarginalFunctionality.calcPosition reports sufficient margin, Exchange.withdrawTo will release proxy-held tokens against these synthetic balances. This breaks the intended invariant that withdrawals must be limited by actual collateral and locked obligations, enabling an anyone-can-take (ACT) opportunity.

2. Key Background

Orion Exchange on BNB Chain is deployed behind an AdminUpgradeabilityProxy at 0xe9d1d2a27458378dd6c6f0b2c390807aed2217ca. At block 39107494, its implementation is an Orion exchange contract that maintains a per-user internal ledger assetBalances[user][asset] and supports:

  • depositAssetTo: credit internal balances when users or contracts deposit BNB/ERC20 tokens.
  • withdrawTo: debit internal balances (subject to margin checks) and transfer actual tokens from the proxy.
  • redeemAtomic: implement atomic redeem flows using LibAtomic and atomicSwaps storage.

The relevant implementation code is drawn from the verified Orion project cloned during data collection (e.g., Exchange.sol, ExchangeWithAtomic.sol, LibAtomic.sol). In that project, ExchangeWithAtomic.redeemAtomic delegates to LibAtomic.doRedeemAtomic to adjust internal balances based on a signed RedeemOrder:

// ExchangeWithAtomic.sol (simplified call site)
function redeemAtomic(LibAtomic.RedeemOrder calldata order, bytes calldata secret) external {
    LibAtomic.doRedeemAtomic(order, secret, secrets, assetBalances, liabilities);
}

The core logic of LibAtomic.doRedeemAtomic is:

// LibAtomic.doRedeemAtomic (key lines)
function doRedeemAtomic(
    LibAtomic.RedeemOrder calldata order,
    bytes calldata secret,
    mapping(bytes32 => bool) storage secrets,
    mapping(address => mapping(address => int192)) storage assetBalances,
    mapping(address => MarginalFunctionality.Liability[]) storage liabilities
) public {
    require(!secrets[order.secretHash], "E17R");
    require(getEthSignedAtomicOrderHash(order).recover(order.signature) == order.sender, "E2");
    require(order.expiration / 1000 >= block.timestamp, "E4A");
    require(order.secretHash == keccak256(secret), "E17");
    secrets[order.secretHash] = true;

    LibExchange._updateBalance(order.sender, order.asset, -1 * int(uint(order.amount)), assetBalances, liabilities);
    LibExchange._updateBalance(order.receiver, order.asset, int(uint(order.amount)), assetBalances, liabilities);
}

Critically, this function only verifies the redeem order signature, expiration, and matching secret hash. It does not reference any atomicSwaps lock entry or ensure that order.amount is backed by an on-chain lock or by existing reserves; it simply debits and credits internal balances for order.sender and order.receiver.

Withdrawals are handled in Exchange.withdrawTo:

// Exchange.withdrawTo (simplified)
function withdrawTo(address assetAddress, uint112 amount, address to) public nonReentrant {
    uint112 safeAmountDecimal = LibUnitConverter.baseUnitToDecimal(assetAddress, amount);

    assetBalances[msg.sender][assetAddress] -= int192(uint192(safeAmountDecimal));
    emit LibExchange.BalanceChange(msg.sender, assetAddress, -int192(uint192(safeAmountDecimal)));

    if (assetBalances[msg.sender][assetAddress] < 0) revert NotEnoughBalance();
    if (!checkPosition(msg.sender)) revert IncorrectPosition();

    if (assetAddress == address(0)) {
        (bool success, ) = to.call{value: amount}("");
        if (!success) revert NotEnoughBalance();
    } else {
        IERC20(assetAddress).safeTransfer(to, amount);
    }

    emit NewAssetTransaction(msg.sender, to, assetAddress, false, safeAmountDecimal, uint64(block.timestamp));
}

As long as assetBalances[msg.sender][assetAddress] remains non-negative and checkPosition(msg.sender) returns true, the contract will transfer real BNB or tokens held by the proxy to the to address.

The adversary model is a standard, unprivileged on-chain attacker that controls multiple EOAs and contracts, can obtain flash liquidity (e.g., via a USDT flash swap), and interacts only with publicly available Orion functions and on-chain information.

3. Vulnerability Analysis & Root Cause Summary

The vulnerability is an internal accounting flaw in Orion's atomic redeem / withdrawal pipeline. LibAtomic.doRedeemAtomic creates internal balances for a receiver purely from a signed RedeemOrder and secret, without tying that balance creation to any actual atomicSwaps lock or reserve-backed obligation. The subsequent withdrawTo function trusts these internal balances (subject only to a portfolio-wide margin check) and releases real assets from the proxy.

The intended invariant is that, for every account, the combination of deposits, locks, and liabilities must ensure that withdrawals cannot exceed the assets genuinely backing that account's position. In the exploited implementation, the invariant is broken because a crafted sequence of RedeemOrders can move large notional amounts into attacker-controlled internal balances without consuming or checking corresponding locks.

An unprivileged adversary can therefore:

  1. Use a flash swap to obtain temporary liquidity.
  2. Deposit a portion into Orion.
  3. Call redeemAtomic repeatedly to fabricate internal balances across multiple assets while preserving margin.
  4. Call withdrawTo to drain BNB and ERC20 tokens from the proxy to an adversary EOA.
  5. Repay the flash swap and keep the residual multi-asset profit.

This behavior is an ACT opportunity because any unprivileged address or cluster can reproduce the same strategy using public code, traces, and price data under the same pre-state.

4. Detailed Root Cause Analysis

4.1 Safety invariant and breakpoint

The safety invariant can be expressed as:

For every user u and asset a, the exchange should ensure that withdrawals and internal balance credits are bounded by the combination of deposits and lock-backed obligations, such that the total claim on reserves does not exceed actual reserves.

Formally, for each user u and asset a over time:

internal_balance[u,a] <= deposits[u,a] + lock_claims[u,a] - liabilities[u,a]

and the sum over all users should not exceed the proxy's external holdings for a.

The code-level breakpoint is in LibAtomic.doRedeemAtomic and ExchangeWithAtomic.redeemAtomic:

  • redeemAtomic invokes LibAtomic.doRedeemAtomic(order, secret, secrets, assetBalances, liabilities).
  • doRedeemAtomic verifies only the signature and secret, then calls _updateBalance to debit order.sender and credit order.receiver for order.asset and order.amount.
  • There is no linkage between order and any atomicSwaps[secretHash] entry; no check that a corresponding lock exists, is unused, or matches order.amount and order.asset.

As a result, the attacker can construct RedeemOrders where order.sender is an internal/broker account and order.receiver is an attacker-controlled account, with large order.amount values, and apply them repeatedly as long as the global margin check remains satisfied.

On the withdrawal side, Exchange.withdrawTo relies on assetBalances[msg.sender][assetAddress] and checkPosition(msg.sender) to prevent over-withdrawal. However, because the internal balances were already inflated by doRedeemAtomic without consuming real locks, these checks operate on corrupted accounting state and permit withdrawals backed only by fabricated balances.

4.2 Adversary-controlled inputs and reachability

The adversary controls:

  • EOA 0x51177db1ff3b450007958447946a2eee388288d2 (seed tx sender and gas payer).
  • Orchestrator contract 0xf8bfac82bdd7ac82d3aeec98b9e1e73579509db6 that encodes the attack sequence.
  • Internal/broker account 0x805d8c96d09f6500cdad501d5c754d2fbf186d7a used within Orion accounting.
  • Profit EOA 0xf7a8c237ac04c1c3361851ea78e8f50b04c76152 (receives drained assets).

The orchestrator is an unprivileged contract; any EOA could deploy an equivalent contract and call the same public functions on the Orion proxy. All exploited functions are publicly callable:

  • depositAssetTo on the proxy for depositing USDT.
  • redeemAtomic on the proxy, delegating to LibAtomic.doRedeemAtomic in the implementation.
  • withdrawTo on the proxy for withdrawing BNB and tokens.

The seed transaction 0x660837a1640dd9cc0561ab7ff6c85325edebfa17d8b11a3bb94457ba6dcae18c is a standard BNB Chain transaction from an unprivileged EOA to the orchestrator with sufficient gas, demonstrating reachability under the ACT model.

4.3 Trace evidence: attack sequence

The trace for 0x6608...e18c (captured in trace.cast.log) shows the following high-level sequence:

  1. 0x5117...d2 calls orchestrator 0xf8bfac82... with zero BNB value.
  2. The orchestrator initiates a USDT flash swap from 0x16b9a82891338f9ba80e2d6970fdda79d1eb0dae.
  3. Using part of the borrowed USDT, the orchestrator calls depositAssetTo on proxy 0xe9d1d2a2... to credit internal USDT balances for attacker-controlled accounts.
  4. The orchestrator then calls redeemAtomic multiple times on the proxy, which delegatecalls into LibAtomic.doRedeemAtomic on implementation 0xc662cea3..., creating large internal balances in BNB and multiple ERC20 tokens for attacker-linked accounts while maintaining a margin-positive portfolio according to calcPosition.
  5. Finally, the orchestrator calls withdrawTo repeatedly, causing the proxy to transfer real BNB and ERC20 tokens to profit EOA 0xf7a8... and to the flash swap provider, then repays the USDT flash swap and leaves the net profit with the adversary.

4.4 Quantitative invariant violation

The balance_diff.json for the seed transaction confirms the invariant violation at the level of the proxy's reserves. For the proxy 0xe9d1d2a2..., native and ERC20 balance deltas include (negative amounts reflect losses by the proxy):

{
  "native_balance_deltas": [
    {
      "address": "0xe9d1d2a27458378dd6c6f0b2c390807aed2217ca",
      "delta_wei": "-79896159740000000000"
    }
  ],
  "erc20_balance_deltas": [
    {"token": "0x55d398326f99059ff775485246999027b3197955", "delta": "-19844686077960000000000"},
    {"token": "0x1d2f0da169ceb9fc7b3144628db156f3f6c60dbe", "delta": "-62444730331000000000000"},
    {"token": "0xf8a0bf9cf54bb92f17374d9e9a321e6a111a51bd", "delta": "-1741195444560000000000"},
    {"token": "0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c", "delta": "-303464280000000000"},
    {"token": "0x2170ed0880ac9a755fd29b2688956bd959f933f8", "delta": "-4808971490000000000"},
    {"token": "0x7083609fce4d1d8dc0c979aab8c869ea2c873402", "delta": "-785588869650000000000"},
    {"token": "0xe4ca1f75eca6214393fce1c1b316c237664eaa8e", "delta": "-49892192920826"},
    {"token": "0xba2ae424d960c26247dd6c32edc70b295c744c43", "delta": "-11261078858282"},
    {"token": "0xadbaf88b39d37dc68775ed1541f1bf83a5a45feb", "delta": "-86361313198550000000000"},
    {"token": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", "delta": "-11001109311140000000000"},
    {"token": "0xbf7c81fff98bbe61b40ed186e4afd6ddd01337fe", "delta": "-237884641240000000000"},
    {"token": "0x1ce0c2827e2ef14d5c4f29a091d735a204794041", "delta": "-198361026380000000000"}
  ]
}

These losses at the proxy are mirrored by gains at the profit EOA 0xf7a8..., confirming that the internal accounting allowed the attacker to withdraw more than any legitimate deposits or locks should have permitted.

5. Adversary Flow Analysis

5.1 Adversary-related cluster

The adversary-related cluster of accounts used in this exploit is:

  • 0x51177db1ff3b450007958447946a2eee388288d2 — EOA that submits the seed transaction and pays gas.
  • 0xf8bfac82bdd7ac82d3aeec98b9e1e73579509db6 — attacker-controlled orchestrator contract implementing the flash swap, Orion interaction, and withdrawals.
  • 0x805d8c96d09f6500cdad501d5c754d2fbf186d7a — broker/internal account within Orion used to route balances.
  • 0xf7a8c237ac04c1c3361851ea78e8f50b04c76152 — profit EOA receiving the drained BNB and ERC20 tokens.

All of these are unprivileged accounts under the ACT adversary model. The orchestrator is not access-controlled and could be called by any EOA with sufficient gas.

5.2 Step-by-step on-chain flow (tx 0x6608...e18c)

  1. Flash swap and funding

    • The orchestrator 0xf8bfac82... initiates a USDT flash swap from 0x16b9a82891338f9ba80e2d6970fdda79d1eb0dae.
    • balance_diff.json shows 0x16b9... temporarily loses 10,400 USDT (delta -10400000000000000000000 at the lender) and later regains it when the flash swap is repaid.
  2. Deposit into Orion

    • Using part of the borrowed USDT, the orchestrator calls depositAssetTo on proxy 0xe9d1d2a2..., crediting internal USDT balances for attacker-controlled accounts.
    • This provides some legitimate collateral and helps keep calcPosition non-negative during the exploit.
  3. RedeemAtomic internal balance fabrication

    • The orchestrator calls redeemAtomic multiple times with carefully crafted RedeemOrders.
    • Each call delegates to LibAtomic.doRedeemAtomic, which:
      • Verifies the order signature and secret.
      • Marks the secret as used.
      • Debits order.sender and credits order.receiver in assetBalances for various assets (BNB proxy representation and multiple ERC20s).
    • Because doRedeemAtomic does not consume or verify locks in atomicSwaps, the attacker can move arbitrarily large nominal amounts into their internal balances, as long as the overall margin check remains satisfied.
  4. Withdrawals and profit realization

    • After inflating internal balances, the orchestrator repeatedly calls withdrawTo on the proxy for multiple assets, with msg.sender set to an attacker-controlled internal account and to set to the profit EOA 0xf7a8... (or the flash swap lender for the repayment).
    • withdrawTo subtracts safeAmountDecimal from the internal balance, checks NotEnoughBalance and IncorrectPosition, and then transfers tokens from the proxy to the target on success.
    • The trace and balance_diff.json show:
      • Proxy 0xe9d1d2a2... loses 79.89615974 BNB (delta -79896159740000000000 wei) and large ERC20 balances across USDT, XRP, LINK, BTCB, ETH, DOT, ORN, DOGE, COTI, USDC, EGLD, and AVAX.
      • Profit EOA 0xf7a8... gains the corresponding positive deltas for those tokens.
      • The flash swap lender 0x16b9... receives back 10,400 USDT.
      • Tx sender 0x5117... pays approximately 0.026438544 BNB in gas (native balance delta -26438544000000000 wei).
  5. Post-tx state

    • The flash swap is fully repaid.
    • The adversary cluster retains a large multi-asset portfolio at the profit EOA, funded entirely by Orion's proxy reserves.

This flow is reproducible by any unprivileged adversary with knowledge of the vulnerability and access to similar flash liquidity, satisfying the ACT criteria.

6. Impact & Losses

6.1 Token-level losses from Orion proxy

From balance_diff.json, the direct on-chain losses from the Orion proxy 0xe9d1d2a2... in transaction 0x6608...e18c are:

  • BNB (native): 79.89615974 BNB (delta -79896159740000000000 wei).
  • USDT (0x55d398326f99059ff775485246999027b3197955): 19,844,686.07796 USDT-equivalent units (delta -19844686077960000000000 with 18 decimals).
  • XRP (0x1d2f0da169ceb9fc7b3144628db156f3f6c60dbe).
  • LINK (0xf8a0bf9cf54bb92f17374d9e9a321e6a111a51bd).
  • BTCB (0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c).
  • ETH (0x2170ed0880ac9a755fd29b2688956bd959f933f8).
  • DOT (0x7083609fce4d1d8dc0c979aab8c869ea2c873402).
  • ORN (0xe4ca1f75eca6214393fce1c1b316c237664eaa8e).
  • DOGE (0xba2ae424d960c26247dd6c32edc70b295c744c43).
  • COTI (0xadbaf88b39d37dc68775ed1541f1bf83a5a45feb).
  • USDC (0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d).
  • EGLD (0xbf7c81fff98bbe61b40ed186e4afd6ddd01337fe).
  • AVAX (0x1ce0c2827e2ef14d5c4f29a091d735a204794041).

For these ERC20 tokens, the exact integer deltas (in base units) are recorded in balance_diff.json and align with the corresponding positive deltas at the profit EOA 0xf7a8....

6.2 Quantified adversary profit in USD

Using the historical USD prices in artifacts/root_cause/data_collector/iter_8/prices/historical_prices_block_39107494_binance.json, which provide Binance spot 1m close prices (or 1.0 USD pegs for USDT/USDC) at or immediately preceding the block timestamp, the adversary-cluster portfolio change can be valued in USD.

The portfolio is restricted to assets with non-zero deltas in this transaction (BNB, USDT, XRP, LINK, BTCB, ETH, DOT, ORN, DOGE, COTI, USDC, EGLD, AVAX) and to addresses in the adversary cluster {0x5117..., 0xf8bfac82..., 0x805d8c96..., 0xf7a8...}. Token-unit deltas come from balance_diff.json and decimals from verified sources or explorer metadata (BNB and main BEP20s: 18 decimals; ORN and DOGE: 8 decimals; other BEP20TokenImplementation assets treated as 18 decimals).

Under these assumptions:

  • The cluster's portfolio value before the transaction is approximately $79.54.
  • The cluster's portfolio value after the transaction is approximately $941,043.75.
  • The tx sender pays gas of about 0.026438544 BNB, worth ~$15.77 at the BNB price of $596.5.
  • The net portfolio gain in USD, after accounting for gas fees, is approximately $940,964.21.

Because BNB gas spending is already included in the cluster's native balance deltas, the reported value_delta_in_reference_asset in the analysis is net of fees; fees are reported separately for clarity.

6.3 ACT opportunity characterization

Given the above, this incident clearly satisfies the ACT profit predicate:

  • The adversary-cluster net portfolio value in USD increases strictly after fees.
  • All required inputs (contract sources, traces, balance diffs, price data) are publicly obtainable.
  • The strategy uses only permissionless, unprivileged interactions with Orion and standard on-chain primitives (flash swap, ERC20 transfers).

Therefore, the root cause is an ACT-style attack: an internal accounting vulnerability in Orion's redeem/withdraw pipeline that any unprivileged adversary can exploit to realize a large, quantified profit.

7. References

  • Seed transaction and state diffs: BNB Chain tx 0x660837a1640dd9cc0561ab7ff6c85325edebfa17d8b11a3bb94457ba6dcae18c (block 39107494), with metadata, trace, and balance diffs in artifacts/root_cause/seed/56/0x6608...e18c/.
  • Victim proxy: AdminUpgradeabilityProxy at 0xe9d1d2a27458378dd6c6f0b2c390807aed2217ca on BNB Chain.
  • Victim implementation sources: Verified Orion implementation project cloned under artifacts/root_cause/data_collector/iter_4/contract/56/0xe2c7ce00e0bbbcd15c691dfcca7434f3b95f0750/source/ (including Exchange.sol, ExchangeWithAtomic.sol, LibAtomic.sol).
  • Implementation project at exploit block: Verified project for implementation 0xc662cea3d8d6660ca97fb9ff98122da69a199cd8 under artifacts/root_cause/data_collector/iter_5/contract/56/0xc662...cd8/source/.
  • Adversary contracts and accounts: Orchestrator 0xf8bfac82bdd7ac82d3aeec98b9e1e73579509db6, profit EOA 0xf7a8c237ac04c1c3361851ea78e8f50b04c76152, broker/internal 0x805d8c96d09f6500cdad501d5c754d2fbf186d7a, flash swap lender 0x16b9a82891338f9ba80e2d6970fdda79d1eb0dae, with txlists in artifacts/root_cause/data_collector/iter_3/address/56/*/txlist.normal.json.
  • Historical prices: Binance 1m kline USD prices for BNB and drained tokens at block 39107494 in artifacts/root_cause/data_collector/iter_8/prices/historical_prices_block_39107494_binance.json.

These references are sufficient for an external reader to independently reconstruct the exploit, verify the invariant violation, and re-compute the adversary's profit in USD.