All incidents

NGP Sell-Hook Reserve Drain

Share
Sep 17, 2025 11:41 UTCAttackLoss: 1,896,191.72 USDTPending manual check5 exploit txWindow: 7h 20m
Estimated Impact
1,896,191.72 USDT
Label
Attack
Exploit Tx
5
Addresses
2
Attack Window
7h 20m
Sep 17, 2025 11:41 UTC → Sep 17, 2025 19:02 UTC

Exploit Transactions

TX 1BSC
0x233f9ab24d1d426327208c73cf1583c3c149ef49da899896848e8cf6115b967f
Sep 17, 2025 11:41 UTCExplorer
TX 2BSC
0x623374bc7bb29e66710ccc236c898bfcef71673631faba05a3024373917af41a
Sep 17, 2025 12:30 UTCExplorer
TX 3BSC
0xa64d60574dbc6a5401cea1366251d26dd8328b4adff500df5175c03e0246689b
Sep 17, 2025 12:31 UTCExplorer
TX 4BSC
0x90ab66a38d34ed1b40dd72177e94e554b8d643fbf2af906f62399f90d51577f6
Sep 17, 2025 13:01 UTCExplorer
TX 5BSC
0xc2066e0dff1a8a042057387d7356ad7ced76ab90904baa1e0b5ecbc2434df8e1
Sep 17, 2025 19:02 UTCExplorer

Victim Addresses

0xd2f26200cd524db097cf4ab7cc2e5c38ab6ae5c9BSC
0x20cab54946d070de7cc7228b62f213fccf3ffb1eBSC

Loss Breakdown

1,896,191.72USDT

Similar Incidents

Root Cause Analysis

NGP Sell-Hook Reserve Drain

1. Incident Overview TL;DR

On BNB Smart Chain, an attacker cluster accumulated NGP through four public router buys, then executed the exploit in tx 0xc2066e0dff1a8a042057387d7356ad7ced76ab90904baa1e0b5ecbc2434df8e1 at block 61515895. The exploit sold 854597929114878917777843 raw NGP through the NGP/USDT PancakeSwap V2 main pair 0x20cab54946d070de7cc7228b62f213fccf3ffb1e while using public flash liquidity and Venus borrow capacity to increase notional size. The sell path caused NGP's transfer hook to remove NGP from the pair and call sync() before the seller transfer finished, so the pair priced the trade from a manipulated reserve state and overpaid USDT. Collector artifacts show the pair's raw USDT balance fell from 2212887451145128834721055 to 9156136012843449018, and profit recipient 0x8618314270528e245fbbb6fba54e245bb61a8d47 received 1896191720695873504350698 raw USDT plus 103619196987339834757 wei BNB.

The root cause is an application-layer bug in the NGP token, not in PancakeSwap itself. NGP embedded fee logic inside Token.sol::_update for sells into mainPair; that logic transferred NGP out of the pair to treasuryAddress and rewardPoolAddress, then forced IUniswapV2Pair(mainPair).sync() before the seller's full NGP amount had been credited. That broke the reserve-accounting invariant assumed by Uniswap V2-style AMMs and made deterministic extraction possible for any unprivileged actor able to accumulate NGP and call the public sell path.

2. Key Background

The relevant public components were the NGP token 0xd2f26200cd524db097cf4ab7cc2e5c38ab6ae5c9, its PancakeSwap V2 main pair 0x20cab54946d070de7cc7228b62f213fccf3ffb1e, Pancake router 0x10ed43c718714eb63d5aa57b78b54704e256024e, Venus markets, and external flash-liquidity venues. PancakeSwap V2 pricing assumes that ERC20 transfers into the pair do not arbitrarily mutate pair balances mid-swap. If a token hook changes pair balances before swap accounting completes, the pair can price against a state that no longer represents the true pre-swap reserve plus the user's deposited input.

Here, flash liquidity and Venus were financing tools, not the vulnerability. The exploit remained permissionless because all required ingredients were publicly observable and callable on-chain: the attacker only needed NGP inventory, access to the public router sell path, and the vulnerable sell hook to remain active.

3. Vulnerability Analysis & Root Cause Summary

The vulnerability class is an AMM-integration bug in token transfer logic. NGP's sell hook treated a sell into the pair as an opportunity to extract fee tokens directly from the pair itself. In the vulnerable to == mainPair branch, the token debited the pair for treasury and reward distributions and immediately synchronized the pair reserves before crediting the seller's transfer in full. That sequence is incompatible with Uniswap V2 accounting, where pricing expects reserves to reflect the unchanged pre-swap state until the input transfer has actually completed.

The violated invariant is: during a Uniswap V2-style sell, the pair's reserves used for pricing must reflect the pre-swap reserve state plus the seller's full deposited input, and token hooks must not remove pair inventory or force reserve synchronization mid-transfer. The code-level breakpoint identified by the analysis is the sell branch of Token.sol::_update:

super._update(mainPair, treasuryAddress, treasuryAmount);
super._update(mainPair, rewardPoolAddress, rewardAmount);
IUniswapV2Pair(mainPair).sync();
super._update(from, to, value);

Once this branch is reachable with enough NGP inventory, the pair will overpay USDT in a deterministic way. That is why the case is ACT: no privileged key, signer, or hidden state was required.

4. Detailed Root Cause Analysis

The ACT opportunity is defined on BSC immediately before the public exploit sequence, with public state reconstructible from the priming-buy metadata, sender transaction histories, exploit receipt, trace, and balance-diff artifacts. The attacker sequence consisted of four public NGP accumulation transactions, exploit-contract deployment, and the final exploit transaction:

0x233f9ab24d1d426327208c73cf1583c3c149ef49da899896848e8cf6115b967f  public NGP buy
0x623374bc7bb29e66710ccc236c898bfcef71673631faba05a3024373917af41a  public NGP buy
0xa64d60574dbc6a5401cea1366251d26dd8328b4adff500df5175c03e0246689b  public NGP buy
0x90ab66a38d34ed1b40dd72177e94e554b8d643fbf2af906f62399f90d51577f6  public NGP buy
0xbb3b5e334f60fc668d2165e2682a1106bd7bb25e60d3c5f340397d983e35f5a3  deploy exploit contract
0xc2066e0dff1a8a042057387d7356ad7ced76ab90904baa1e0b5ecbc2434df8e1  exploit execution

The three supplier EOAs accumulated the exact NGP balances later transferred into the exploit contract: 226951594368112994017390, 426926630852585941165000, and 200719703894179982595453 raw units. Sender 0x0305ddd42887676ec593b39ace691b772eb3c876 deployed exploit contract 0x2d2a69bdafe4aad981da4e98721b3b81a0315363, then triggered the exploit transaction.

The exploit receipt shows those three EOAs transferring NGP into the exploit contract at the start of the exploit. The balance-diff artifact for the same transaction shows the pair lost 2212878295009115991272037 raw USDT and the profit recipient gained 1896191720695873504350698 raw USDT. The validator's independent forked PoC reproduces the critical mechanism and captures the same semantic breakpoint in trace form:

Transfer(pair -> treasury, 341316632425037758818857 NGP)
Transfer(pair -> rewardPool, 136526652970015103527543 NGP)
NGP_USDT_MainPair::sync()
Sync(213759688435302745918935890 USDT, 35000000000000001 NGP)

That sequence shows the pair's NGP side being drained and resynchronized before the attacker transfer is fully reflected in reserves. Immediately afterward, the pair pays out 213759679279166733075486872 raw USDT to the attacker in the PoC reproduction, leaving only 9156136012843449018 raw USDT in the pair. This directly matches the claimed reserve-accounting failure: the pair is induced to quote against an artificially shrunken NGP reserve and therefore over-disburses USDT.

5. Adversary Flow Analysis

The adversary cluster was:

  • 0x0305ddd42887676ec593b39ace691b772eb3c876: deploying and coordinating EOA.
  • 0x2d2a69bdafe4aad981da4e98721b3b81a0315363: attacker-deployed execution contract.
  • 0x76b1528feaae231c4e1edd837c741fcd03e98086, 0x71a94b29bb59bd8a3bab04960e18f4dcdc2faf43, 0x9ef16a6ea72ab6092369e0ec7be89b411942a311: supplier EOAs that accumulated and forwarded NGP.
  • 0x8618314270528e245fbbb6fba54e245bb61a8d47: immediate profit-recipient EOA.

The on-chain flow was:

  1. The supplier EOAs executed four public buys through the router to accumulate NGP inventory.
  2. The coordinator deployed the exploit contract in tx 0xbb3b5e334f60fc668d2165e2682a1106bd7bb25e60d3c5f340397d983e35f5a3.
  3. In tx 0xc2066e0dff1a8a042057387d7356ad7ced76ab90904baa1e0b5ecbc2434df8e1, the exploit contract collected all pre-bought NGP, used public flash liquidity and Venus capacity to scale the trade, and sold through the vulnerable NGP hook.
  4. The hook drained pair-side NGP into treasury and reward addresses, called sync(), and caused the pair to overpay USDT.
  5. The exploit contract repaid temporary liquidity and sent the realized USDT and BNB to 0x8618314270528e245fbbb6fba54e245bb61a8d47.

This flow is end-to-end complete and permissionless. The access-controlled attacker contract did not make the opportunity non-ACT; it was merely a custom execution wrapper around public protocol calls.

6. Impact & Losses

The measurable market loss was approximately 1.896M USDT-equivalent realized to the profit recipient, with additional BNB proceeds also recorded. The root_cause.json loss section records:

[
  {
    "token_symbol": "USDT",
    "amount": "1896191720695873504350698",
    "decimal": 18
  }
]

This matches the collector balance-diff evidence. The victim side was the NGP token's primary PancakeSwap liquidity venue: NGP token 0xd2f26200cd524db097cf4ab7cc2e5c38ab6ae5c9 and NGP/USDT pair 0x20cab54946d070de7cc7228b62f213fccf3ffb1e. The exploit depleted value from the pool by corrupting reserve accounting during the sell path.

7. References

  • Seed exploit metadata for tx 0xc2066e0dff1a8a042057387d7356ad7ced76ab90904baa1e0b5ecbc2434df8e1
  • Seed exploit trace and balance diff for the same transaction
  • Exploit receipt from collector iter_1
  • Priming-buy metadata for txs 0x233f9ab24d1d426327208c73cf1583c3c149ef49da899896848e8cf6115b967f, 0x623374bc7bb29e66710ccc236c898bfcef71673631faba05a3024373917af41a, 0xa64d60574dbc6a5401cea1366251d26dd8328b4adff500df5175c03e0246689b, and 0x90ab66a38d34ed1b40dd72177e94e554b8d643fbf2af906f62399f90d51577f6
  • Sender and supplier transaction histories under the collector address artifacts
  • Verified NGP source at https://bscscan.com/address/0xd2f26200cd524db097cf4ab7cc2e5c38ab6ae5c9#code
  • Validator fork trace in /workspace/session/artifacts/validator/forge-test.log, which independently reproduces the pair-side fee drains, mid-swap sync(), and near-total USDT depletion