All incidents

KEKESANTA Pair-to-Router Double-Credit Liquidity Drain

Share
Dec 16, 2023 10:40 UTCAttackLoss: 9.2 WBNBPending manual check1 exploit txWindow: Atomic
Estimated Impact
9.2 WBNB
Label
Attack
Exploit Tx
1
Addresses
2
Attack Window
Atomic
Dec 16, 2023 10:40 UTC → Dec 16, 2023 10:40 UTC

Exploit Transactions

TX 1BSC
0x2fcee04e64e54f3dd9c15db9ae44e4cbdd57ab4c6f01941a3acf470dc60bfc16
Dec 16, 2023 10:40 UTCExplorer

Victim Addresses

0x2d9ffa7ea5d1aaaba58e60168517b49f57e7f85bBSC
0x7dda132dd57b773a94e27c5caa97834a73510429BSC

Loss Breakdown

9.2WBNB

Similar Incidents

Root Cause Analysis

KEKESANTA Pair-to-Router Double-Credit Liquidity Drain

1. Incident Overview TL;DR

On BSC block 34402344, transaction 0x2fcee04e64e54f3dd9c15db9ae44e4cbdd57ab4c6f01941a3acf470dc60bfc16 exploited KEKESANTA token 0x7dda132dd57b773a94e27c5caa97834a73510429 and economically harmed the KEKESANTA/WBNB Pancake pair 0x2d9ffa7ea5d1aaaba58e60168517b49f57e7f85b. The adversary used only public infrastructure: a flash-loan source, Pancake router 0x10ed43c718714eb63d5aa57b78b54704e256024e, Pancake pair logic, and the token's public transfer paths.

The root cause is a transfer-accounting bug in KEKESANTA. Its _transfer routine treats isMarket(from) and isMarket(to) as separate if branches rather than mutually exclusive modes. When a transfer is routed from the Pancake pair to the Pancake router, both conditions are true, so the sender is debited once but the recipient and fee wallet are credited twice. That creates extra KEST without changing totalSupply, lets the attacker recycle the duplicated tokens through public router flows, and drains WBNB from the pair.

2. Key Background

KEKESANTA hard-codes two addresses as market endpoints: the Pancake router and the KEKESANTA/WBNB pair. That classification matters because the token charges different fees when from is a market versus when to is a market. The implementation assumes those cases are mutually exclusive, but Pancake routing legitimately creates transfers where the pair sends tokens to the router.

Two public Pancake paths are especially important:

  • swapTokensForExactTokens(..., to=router) causes the pair to send output tokens directly to the router.
  • removeLiquidityETHSupportingFeeOnTransferTokens(...) calls PancakePair::burn(router), which also sends pair assets to the router.

Those pair-to-router transfers are normal Pancake behavior. The problem is that KEKESANTA's transfer logic misprices them and mints accounting-only KEST along the way.

3. Vulnerability Analysis & Root Cause Summary

The vulnerability class is a broken token-accounting implementation in a fee-on-transfer token integrated with a DEX. KEKESANTA snapshots the sender balance once, then separately processes a buy-fee branch and a sell-fee branch. If both endpoints are markets, both branches execute against the same cached sender balance.

The relevant code in KEKESANTA is:

uint256 fromBalance = _balances[from];

if (isMarket(from)) {
    uint fee = takeAFee(amount, buyFee);
    _balances[from] = fromBalance - amount;
    _balances[to] += amount - fee;
    _balances[marketWallet] += fee;
}

if (isMarket(to)) {
    uint fee = takeAFee(amount, sellFee);
    _balances[from] = fromBalance - amount;
    _balances[to] += amount - fee;
    _balances[marketWallet] += fee;
}

This violates the token-balance conservation invariant: for a normal transfer, the sender debit plus the recipient credit plus fee credit must net to zero unless explicit mint or burn logic updates supply. Here, totalSupply stays fixed, but the recipient and fee wallet are credited twice while the sender ends at only fromBalance - amount. The result is synthetic KEST that the attacker can sell or turn into LP value.

4. Detailed Root Cause Analysis

4.1 Code-Level Breakpoint

The bug sits in KEKESANTA::_transfer. KEKESANTA defines isMarket(user) as true when user is either the Pancake router or the Pancake pair. That means a pair-to-router transfer satisfies both isMarket(from) and isMarket(to).

function isMarket(address user) internal view returns (bool) {
    if (user == address(_router) || user == address(_pair)) {
        return true;
    } else {
        return false;
    }
}

Pancake's pair contract legitimately emits exactly those transfers during swap and burn paths:

function burn(address to) external lock returns (uint amount0, uint amount1) {
    ...
    _safeTransfer(_token0, to, amount0);
    _safeTransfer(_token1, to, amount1);
    ...
}

function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
    ...
    if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out);
    if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out);
    ...
}

So the invariant break is deterministic:

  1. Pancake causes the KEKESANTA/WBNB pair to transfer KEST to the router.
  2. KEKESANTA enters _transfer(pair, router, amount).
  3. Both market branches run against the same cached fromBalance.
  4. The pair loses amount once, but the router and market wallet each receive two credits.
  5. The attacker then reuses the extra KEST in later liquidity and swap steps.

4.2 On-Chain Exploit Path

The seed trace shows the exploit contract taking a public flash loan of 200 WBNB, then executing the market-to-market cycle through router and pair calls. Representative trace events include:

0xd50Cf00b6e600Dd036Ba8eF475677d816d6c4281::flashLoan(..., [200000000000000000000], ...)
PancakeRouter::swapTokensForExactTokens(334886494892597876400352453291, 199368246265042846651, [...], PancakeRouter, ...)
PancakeRouter::removeLiquidityETHSupportingFeeOnTransferTokens(KEKESANTA, 1000000000000000, 1, 1, 0xC25979956D6f6AcFc3702C68DFf7a4d871Eee4aa, ...)
PancakePair::burn(PancakeRouter)
WBNB::transfer(0x90c4C1aa895a086215765EC9639431309633B198, 9019657610212775442)
PancakePair::transfer(0x90c4C1aa895a086215765EC9639431309633B198, 2146320284844256899722)

The sequence is important. The attacker does not need privileged functions, fee exemptions, attacker-specific bytecode, or private orderflow. Public interfaces are enough:

  1. Borrow WBNB through the public flash-loan entrypoint.
  2. Buy KEST and mint a starter LP position.
  3. Force pair-to-pair and pair-to-router transfers through swap and liquidity-removal flows.
  4. Let KEKESANTA mis-account those transfers and inflate tracked KEST balances.
  5. Sell the duplicated KEST back into WBNB and keep residual LP claims.
  6. Repay the flash loan plus fee.

4.3 Deterministic Success Predicate

The sender EOA 0x90c4c1aa895a086215765ec9639431309633b198 had 989051348300000000 wei native BNB before the exploit and 971767348300000000 wei native BNB after it. The same transaction also transferred 9019657610212775442 wei WBNB to that EOA. Treating BNB and WBNB as 1:1 on BSC gives a deterministic lower-bound post-state portfolio value of 9991424958512775442 wei.

After subtracting the 17284000000000000 wei gas cost, the EOA realized a lower-bound gain of 9002373610212775442 wei. That lower bound excludes the 2146320284844256899722 Pancake LP tokens transferred to the EOA and the 26329693359224618059986 LP tokens retained by the exploit contract, so it understates the full economic result.

5. Adversary Flow Analysis

The identified adversary cluster is:

  • EOA 0x90c4c1aa895a086215765ec9639431309633b198, which submitted the exploit transaction and received final payouts.
  • Contract 0xc25979956d6f6acfc3702c68dff7a4d871eee4aa, which executed the exploit logic in-transaction.

The exploit unfolded in three stages.

5.1 Flash-Loan Bootstrap

The exploit contract borrowed 200 WBNB, used a small 0.01 WBNB buy to acquire KEST, transferred WBNB and KEST into the pair, and minted initial LP. This stage created the first LP foothold needed to access Pancake's public burn path.

5.2 Market-to-Market Amplification

The exploit then repeated a router-mediated loop:

  • buy additional KEST,
  • push KEST into the pair,
  • trigger a pair swap,
  • request a router-targeted swap output,
  • remove a small amount of liquidity through the router,
  • sell most of the resulting KEST back into WBNB.

Each router-targeted output and burn path generated pair-to-router KEST transfers. Those were the exact transfers that caused both KEKESANTA market branches to execute and break balance conservation.

5.3 Cash-Out and Repayment

At the end of the transaction, the exploit contract transferred 9019657610212775442 wei WBNB and 2146320284844256899722 LP tokens to the submitting EOA, and the balance-diff artifact records another 26329693359224618059986 LP tokens at post-state holder 0x0ed943ce24baebf257488771759f9bf482c39706, which the root-cause artifact attributes to the residual exploit path. The flash loan plus fee was then repaid, and the entire exploit completed in one public transaction.

6. Impact & Losses

The direct measurable pool loss is 9199657663494314480 raw units of WBNB (9.19965766349431448 WBNB). The balance-diff artifact also shows the pair's KEST balance expanding from 477302959604163425155246829468 to 18597825900069617926618400412389, which confirms that the pool was left holding massively inflated KEST inventory while losing WBNB.

That means the incident harmed LPs in two ways:

  • the pair's WBNB reserve was drained, and
  • the remaining pool composition was skewed toward artificially inflated KEST balances.

The incident is therefore an ACT exploit with concrete economic loss, not a theoretical accounting anomaly.

7. References

  1. Seed transaction metadata for 0x2fcee04e64e54f3dd9c15db9ae44e4cbdd57ab4c6f01941a3acf470dc60bfc16.
  2. Seed execution trace showing the flash loan, router swap/burn loop, and final payouts.
  3. Verified KEKESANTA source at 0x7dda132dd57b773a94e27c5caa97834a73510429, especially _transfer and isMarket.
  4. Verified PancakePair source at 0x2d9ffa7ea5d1aaaba58e60168517b49f57e7f85b, especially swap and burn.
  5. Balance-diff artifact showing the native BNB gas delta, the pair's KEST growth, and the LP-token payouts.