October 20, 2026
|
Exploit Postmortem
Cover image for "BNB Chain DEX Router's $46K Loss and How Olympix Would Have Prevented It"

BNB Chain DEX Router's $46K Loss and How Olympix Would Have Prevented It

Date: September 7, 2026

Chain: BNB Chain

Loss: 62.28 WBNB, about $46K, taken from 29 wallets that had approved the router

Class: swap callback that pays any caller from any approved wallet

None of the 29 wallets drained on September 7, 2026 signed anything that day. Each of them had approved a swap router on BNB Chain at some earlier point, the routine step every DEX asks for before a first trade, and the approval sat there waiting. At 16:15:13 UTC, a single transaction from a fresh attacker address used those approvals to pull seven different tokens out of all 29 wallets at once.

The attacker sold everything for 62.28 WBNB, worth about $46,000, and repaid the 1 WBNB they'd flash-borrowed to get started, all inside that one transaction. The router's callback function, the piece of code a liquidity pool calls back into to collect payment for a swap, would move tokens from any wallet named in the call to any contract that called it, and it never checked whether that contract was a real pool.

A Contract Built to Collect Payment

Concentrated-liquidity DEXs on the Uniswap V3 model settle swaps in an unusual order. The pool sends the output tokens first, then calls a function named uniswapV3SwapCallback on whoever started the swap and expects to be paid what it's owed before the call ends. A router sits between users and pools, so its callback is where user funds actually move: it runs transferFrom on the user's wallet and pays the pool from it.

That design makes the callback the most sensitive function a router has. Users approve the router once, often for an unlimited amount, so the router can pull exactly what each swap needs. The approval doesn't care which swap is running or who asked for it. Whatever the router decides to pull, the token contract will release.

The router at 0xa331fde028e6F17425AB9333c39ae43722340d24 is an upgradeable proxy whose logic lives in a separate implementation contract. None of the security alerts named the project behind it, and Crypto Times reported that the router's team hadn't confirmed the findings when it published.

Two Checks the Callback Never Made

SlowMist's alert identifies two missing checks in the router's implementation, reached through a swap entry point with selector 0x33411b5e and the uniswapV3SwapCallback it relied on. The router didn't verify that msg.sender, the contract calling the callback, was a legitimate V3 pool derived from a trusted factory, token pair and fee tier. It also didn't bind the payer in the callback to the original swap, so the address the router charged was whatever address arrived in the callback's data.

Either check alone would have stopped this attack. A router that recomputes the pool address from the factory and the pool's tokens and fee, and reverts unless the caller matches, can't be called by a contract the attacker deployed a minute earlier. A router that only ever charges the wallet that started the swap can't be pointed at a stranger's wallet. According to ExVul's analysis as reported by Crypto Times, the router's factoryV3 setting was 0x0, which left it no factory to check a pool against even in principle.

The lesson for builders is that a callback is an entry point like any other. Anyone can deploy a contract that implements the pool interface and call the router's callback directly, so the callback has to prove its caller is a real pool before it touches anyone's approval. A bank that pays out to anyone who says "the customer sent me" has handed every customer's account to whoever learns that sentence.

One Transaction, Twenty-Nine Wallets

The attack ran entirely in the constructor of a contract deployed by 0xb929C7215c0ec8EbAD5fBf73b1Da63bccfFf1896, in block 120524634. The attacker's executor contract first borrowed 1 WBNB from a PancakeSwap pair, which ExVul describes as a flash swap to cover the trades, and spent a few hundredths of it buying small amounts of several tokens.

From there, a fake pool contract at 0xaeaedec462bc82e27faf6efedd8ff95e1d526273 called the router's callback over and over, each time naming one of the victims as the payer. The token logs show the result plainly: every stolen token moved from a victim's wallet to that fake pool, under approvals the victims had granted to the router, and then on to the attacker's executor. The 29 wallets held seven different tokens between them. Twenty-three of the wallets gave up a single token, roughly 360 to 470 units each, and the address SlowMist names as the victim lost two token balances across 16 separate pulls. One wallet lost about 6.92 USDC.

The executor sold each token into its liquidity pool for WBNB, repaid the 1 WBNB loan with 1.0025 WBNB, and unwrapped 62.28 WBNB to native BNB. ExVul's analysis, as Crypto Times summarized it, found no price manipulation, no oracle trick and no compromised key involved. The attacker didn't need any of them, because the router's own approvals did the work.

Approvals That Outlast the Attack

Because the victims' tokens moved under approvals they'd already granted, they never had to click anything for the loss to happen, and the same approvals stay live until each wallet revokes them. On October 20, 2026, one of the drained wallets still showed an unlimited allowance to the router for the token it lost. SlowMist's alert warned that any user with a sufficient allowance to the router could have approved assets drained without further interaction, and Crypto Times advised anyone who'd used it to revoke approvals they no longer needed.

How Olympix Spots a Callback That Trusts Its Caller

Olympix reads a router the way an attacker does, as a set of states, transitions and entry points, and it takes in the context around the code, such as how a V3 pool is expected to call back and what the router's approvals are for, to infer the invariants the system depends on but never writes down. The state that matters here is every user's allowance to the router, and every path that can spend it is worth mapping. Olympix's static analysis surfaces the signal in this callback: a public function that calls transferFrom using a payer address taken from caller-supplied data, with no check that msg.sender is a pool the router's own factory would produce.

The invariant fits in one sentence: the router should only ever spend a user's allowance to pay for a swap that user started, and only to a genuine pool. BugPoCer takes that rule and tries to break it. It runs Counterexample-Guided Invariant Refutation, working outward from the router as a whole, inferring the invariant from how the swap functions and the callback fit together, and executing paths until one violates it. The path doesn't take long to find, since the callback is reachable by any contract and nothing on that path ties the payer to the caller.

The result is a counterexample with concrete steps. A fresh contract that implements the pool interface calls uniswapV3SwapCallback with an arbitrary wallet as the payer, the router calls transferFrom on that wallet's tokens, and the tokens arrive at the fake pool. BugPoCer returns that sequence with the minimal conditions it needs, a victim with a nonzero allowance and nothing more, plus a generated Forge test that replays it. That's the September 7 attack, produced in development instead of on mainnet.

Olympix has flagged this class before in other codebases, where data a caller controls decides whose funds move. It runs automatically as code is written, at every merge and before release, so a router change that drops the pool check or the payer binding shows up the moment it's introduced.

Callbacks Need a Bouncer

This router held no funds of its own, and it still lost $46K, because its approvals gave it reach into every wallet that had ever traded through it. Any contract that pulls tokens on a user's behalf owes those users two checks: that the caller is who it claims to be, and that the person paying is the person who asked. If your contracts pay out through callbacks and you'd like to see every path that reaches an approval, along with a runnable counterexample for each one that breaks, book a technical walkthrough.

On-Chain References

Protocol: Unnamed DEX swap router on BNB Chain

Chain: BNB Chain

Date: September 7, 2026, 16:15:13 UTC

Loss: 62.28 WBNB (about $46K) from 29 wallets

Attacker EOA: 0xb929...1896

Contract deployed by the exploit tx: 0xd034...e351

Attacker executor: 0x46ad...af6e

Fake pool: 0xaeae...6273

Vulnerable router (proxy): 0xa331...0d24

Router implementation: 0xc700...db7f

Victim named by SlowMist: 0xb521...7282

Exploit tx: 0x40eb...9319

Block: 120524634

Sources

SlowMist: TI Alert on the BNB Chain Router Drain

Crypto Times: BNB Chain DEX Router Reportedly Drained of 62 WBNB in Exploit

KuCoin News: BNB Chain Router Attack Results in Loss of 62.28 BNB

More from Olympix:

No items found.

Ready to Shift Security Assurance In-House? Talk to Our Security Experts Today.