All incidents

AFX/AHT addLiquidityUsdt abuse drains treasury USDT liquidity

Share
Feb 10, 2026 11:39 UTCAttackLoss: 10,699.62 USDTManually checked1 exploit txWindow: Atomic

Root Cause Analysis

AFX/AHT addLiquidityUsdt abuse drains treasury USDT liquidity

1. Incident Overview TL;DR

In BSC block 80395411, transaction 0x380cd298a607d4422edc640b7f5a907ec0792841ee5fc963d265b1189397c905 used a PancakeSwap V2 flash swap plus an unverified liquidity-manager contract at 0x560d3973ee82a318d381c49fcbf3ce9d6cf1250b to create a deterministic extraction path into USDT. The attacker-controlled call path caused 0x560d... to move large AFX/AHT amounts (via transferFrom) from a third-party treasury address and to perform liquidity operations, enabling the attacker to drain USDT from the AFX/USDT Pancake pair.

The root cause is that 0x560d...::addLiquidityUsdt(uint256) (selector 0xb1a87f2c) is callable by a non-owner and performs privileged treasury-moving operations and transfers tokens to msg.sender. An unprivileged caller can force liquidity actions that, when composed with flash swaps and router swaps, results in deterministic USDT profit.

2. Key Background

PancakeSwap V2 pairs support flash swaps by allowing swap(..., to, data) to invoke to.pancakeCall(...) when data is non-empty. This enables a borrower (here the attacker-controlled contract 0x129b803f5e8e36e2d6e705d84bbe7995b02fc0cb) to receive tokens, run arbitrary logic, and repay the pair within the same transaction.

The attack involves these on-chain components on BSC (chainid 56):

  • AFX token: 0xb6761b4d7b913ef048c92e3bb1305883422e819a
  • AHT token: 0xcd1ec887b081cfba30c8003e8ad1b67f92236c7b
  • USDT token (BEP20): 0x55d398326f99059ff775485246999027b3197955
  • PancakeSwap V2 Router: 0x10ed43c718714eb63d5aa57b78b54704e256024e
  • PancakeSwap V2 pair (AFX/USDT): 0x9d2d5cc00c8cf233aefd8a177b904f8d3abb5d46
  • PancakeSwap V2 pair (AFX/AHT): 0x63e97b4f292b6cd059fc5f7621291c7ad5b94ce0
  • PancakeSwap V2 pair (flash-swap source used to borrow AFX): 0x01b3beeea8d6892e4dd9f9cfe8045e49889eb489
  • Unverified liquidity manager (vulnerable component): 0x560d3973ee82a318d381c49fcbf3ce9d6cf1250b

AHT is fee-on-transfer. The verified AHT source shows _buyDestroyFee = 45 and _swapFee = 5, applied on buys from swap pairs:

uint256 public _buyDestroyFee = 45;
uint256 public _swapFee = 5;
...
} else if (_swapPairList[sender]) {
    // buy
    destroyFeeAmount = (tAmount * _buyDestroyFee) / 100;
    swapFeeAmount = (tAmount * _swapFee) / 100;
}

3. Vulnerability Analysis & Root Cause Summary

This is an ATTACK-class ACT opportunity caused by missing access control on a privileged liquidity-manager routine.

The vulnerable contract 0x560d... exposes an externally callable function addLiquidityUsdt(uint256) which:

  1. Accepts calls from a non-owner attacker-controlled contract without reverting.
  2. Pulls substantial AFX and AHT amounts from a third-party address (0x146933f2692f5ff3b62441ab3c2a65ddcaca753c) via transferFrom, implying a standing allowance from that address to the liquidity manager.
  3. Performs Pancake router liquidity operations using those pulled assets.
  4. Transfers resulting assets (notably AFX and USDT) to msg.sender (the attacker-controlled caller), rather than to a protocol-controlled recipient.

The net effect is that an unprivileged caller can convert a tiny USDT input (100 USDT base units in the seed transaction) into a large AFX position sourced from third-party allowances, use that AFX to repay the flash swap, and finally swap residual AFX into USDT to realize profit.

4. Detailed Root Cause Analysis

4.1 Deterministic Breakpoint: Unprivileged Call to addLiquidityUsdt

At block 80395411, the liquidity manager’s owner() is 0x1301602a6B70fC0eB86A9aADeC9298d313628b3A (distinct from the attacker-controlled EOA/contract addresses used in the transaction), yet addLiquidityUsdt(100) executes successfully during the exploit transaction. The on-chain trace shows the attacker-controlled contract calling:

0x560d3973EE82a318d381c49fcbF3ce9d6CF1250B::addLiquidityUsdt(100)

Within that same call, the trace shows two critical categories of effects:

  1. Treasury-side pulls via allowance (third-party -> LiquidityManager):
AFX::transferFrom(0x146933F2692F5fF3b62441AB3C2a65dDCAca753c, 0x560d3973EE82a318d381c49fcbF3ce9d6CF1250B, 511965516492998991427845)
AHT::transferFrom(0x146933F2692F5fF3b62441AB3C2a65dDCAca753c, 0x560d3973EE82a318d381c49fcbF3ce9d6CF1250B, 12604468958762124545)
  1. Caller-directed outputs (LiquidityManager -> attacker-controlled caller):
AFX::transfer(0x129b803F5E8e36e2d6e705D84BBe7995b02FC0CB, 511965516492998991426086)
BEP20USDT::transfer(0x129b803F5E8e36e2d6e705D84BBe7995b02FC0CB, 1)

These two behaviors are sufficient to characterize the root cause at a code-level invariant:

  • Intended invariant: only authorized operators should be able to trigger routines that move third-party/treasury assets and route resulting assets to arbitrary recipients.
  • Observed breakpoint: addLiquidityUsdt(uint256) executes for an attacker-controlled non-owner caller and routes assets to that caller while pulling large balances from a third-party address.

4.2 Profit Realization: Pool USDT Depletion and Attacker USDT Gain

After using the liquidity-manager side effects to obtain AFX and complete the flash-swap repayment, the attacker swaps remaining AFX into USDT via Pancake router, sending proceeds to the EOA sender 0x236f08d8962e1f29700e3d91009bfa8d37d71e53. The trace shows the final USDT transfer to the EOA:

PancakePair::swap(10699618671790673809279, 0, 0x236f08d8962e1F29700e3D91009bfa8D37D71e53, 0x)
  BEP20USDT::transfer(0x236f08d8962e1F29700e3D91009bfa8D37D71e53, 10699618671790673809279)

The prestate balance-diff confirms the corresponding USDT depletion from the AFX/USDT pool and USDT gain by the attacker EOA (amounts in USDT base units, 18 decimals):

{"token":"0x55d398326f99059ff775485246999027b3197955","holder":"0x9d2d5cc00c8cf233aefd8a177b904f8d3abb5d46","before":"100612192638198552496590","after":"89912573966407878687395","delta":"-10699618671790673809195","contract_name":"BEP20USDT"}
{"token":"0x55d398326f99059ff775485246999027b3197955","holder":"0x236f08d8962e1f29700e3d91009bfa8d37d71e53","before":"15506610461385075122126","after":"26206229133175748931405","delta":"10699618671790673809279","contract_name":"BEP20USDT"}

The attacker contract 0x129b... also spends a negligible net amount of USDT during the same transaction (100 -> 1, net -99 base units), consistent with the exploit requiring only a tiny USDT input:

{"token":"0x55d398326f99059ff775485246999027b3197955","holder":"0x129b803f5e8e36e2d6e705d84bbe7995b02fc0cb","before":"100","after":"1","delta":"-99","contract_name":"BEP20USDT"}

5. Adversary Flow Analysis

The exploit is realized in a single adversary-crafted transaction:

  1. EOA 0x236f08d8962e1f29700e3d91009bfa8d37d71e53 calls the attacker-controlled contract 0x129b803f5e8e36e2d6e705d84bbe7995b02fc0cb.
  2. 0x129b... triggers a PancakeSwap V2 flash swap on 0x01b3beeea8d6892e4dd9f9cfe8045e49889eb489::swap(...) to borrow a large amount of AFX, which causes the pair to call back into 0x129b...::pancakeCall(...).
  3. Inside pancakeCall, 0x129b... approves USDT/AFX/AHT for router use and swaps AFX -> AHT using swapExactTokensForTokensSupportingFeeOnTransferTokens, paying AHT’s transfer fees along the way.
  4. Still inside pancakeCall, 0x129b... calls 0x560d...::addLiquidityUsdt(100). This is the root-cause breakpoint: the unprivileged call executes and, during execution, the liquidity manager pulls large AFX/AHT balances from 0x146933... via transferFrom and routes assets back to the caller (0x129b...).
  5. 0x129b... swaps AHT -> AFX to acquire enough AFX to repay the flash swap plus fees, and transfers repayment AFX back to the flash-swap pair.
  6. With remaining AFX, 0x129b... swaps AFX -> USDT through the Pancake router and directs the USDT output to the EOA 0x236f..., completing the profit predicate.

6. Impact & Losses

The measurable impact in the seed transaction is a direct depletion of USDT from the AFX/USDT Pancake pair and a corresponding USDT increase for the attacker EOA:

  • AFX/USDT pair 0x9d2d5cc00c8cf233aefd8a177b904f8d3abb5d46: -10699618671790673809195 USDT base units (=-10699.618671790673809195 USDT, 18 decimals).
  • Attacker EOA 0x236f08d8962e1f29700e3d91009bfa8d37d71e53: +10699618671790673809279 USDT base units (=+10699.618671790673809279 USDT, 18 decimals).

The attacker EOA pays gas in BNB; the native balance diff shows -54547150000000 wei (0.00005454715 BNB) for this transaction. The report does not compute a USD valuation for gas fees.

7. References

  • Seed transaction (BSC): 0x380cd298a607d4422edc640b7f5a907ec0792841ee5fc963d265b1189397c905 at block 80395411.
  • Vulnerable contract (unverified LiquidityManager): 0x560d3973ee82a318d381c49fcbf3ce9d6cf1250b, function addLiquidityUsdt(uint256) (selector 0xb1a87f2c).
  • Attacker EOA / profit recipient: 0x236f08d8962e1f29700e3d91009bfa8d37d71e53
  • Attacker helper/orchestrator contract: 0x129b803f5e8e36e2d6e705d84bbe7995b02fc0cb
  • AFX token: 0xb6761b4d7b913ef048c92e3bb1305883422e819a
  • AHT token: 0xcd1ec887b081cfba30c8003e8ad1b67f92236c7b
  • USDT token: 0x55d398326f99059ff775485246999027b3197955
  • PancakeSwap V2 Router: 0x10ed43c718714eb63d5aa57b78b54704e256024e
  • PancakeSwap V2 pair (AFX/USDT): 0x9d2d5cc00c8cf233aefd8a177b904f8d3abb5d46
  • PancakeSwap V2 pair (AFX/AHT): 0x63e97b4f292b6cd059fc5f7621291c7ad5b94ce0
  • PancakeSwap V2 pair (flash-swap source): 0x01b3beeea8d6892e4dd9f9cfe8045e49889eb489
  • Collected artifacts: artifacts/collector/seed/56/0x380cd298a607d4422edc640b7f5a907ec0792841ee5fc963d265b1189397c905/metadata.json, artifacts/collector/seed/56/0x380cd298a607d4422edc640b7f5a907ec0792841ee5fc963d265b1189397c905/trace.cast.log, artifacts/collector/seed/56/0x380cd298a607d4422edc640b7f5a907ec0792841ee5fc963d265b1189397c905/balance_diff.json, artifacts/collector/seed/56/0xcd1ec887b081cfba30c8003e8ad1b67f92236c7b/src/Contract.sol, artifacts/auditor/iter_0/owner_0x560d.txt.