KHABY Fan Token Rug Pull

Reported loss $33K
Rugpull

What happened

KHABY Fan Token (KFT) is a BEP20 token on BNB Chain, deployed on 2022-10-12 by 0xACC1e2D3…2f832c. CertiK Alert flagged it the same day as a copycat KHABY Fan Token whose price had dropped 100%, giving its address as 0xe1e17b24…92f6eE. The verified source gives the owner three levers over holders: setUserReward makes every transfer out of a chosen address revert, setAdminReward credits an address with balance that is not backed by supply each time it sends, and upS sets the sell tax with no upper limit. The owner still reads as the deployer on 2026-09-30.

De.Fi's REKT database lists the incident as an exit scam / rug pull of 32,814 USD: the token deployer added liquidity for 250 WBNB and after some period removed 373 WBNB from the pool, which dropped the KFT price by 100%, and the funds were moved to another EOA address. The listed scammer address is 0xacc1e2d3…2f832c and the listed liquidity-removal transaction is 0xb1f52dc5…9b484a. BscScan returned 403 for that transaction, so it could not be opened, and no transaction opened for this draft shows the owner functions below being called.

How it happened

  1. The constructor mints the whole supply to the deployer and sets the tax: _mtin(msg.sender, totalSupply_ * 10**decimals());, _defaultSellfire = 4;, _defaultBuyfire = 0;, DEVAddress = msg.sender;. Total supply on-chain is 5,000,000,000 tokens.
  2. The owner can block any single holder from sending. setUserReward stores _rewards[accbount] = int256(0) - int256(_totalSupply);, and _recF, which runs for every transfer and transferFrom, then executes _balances[from] = _balances[from].sub(uint256(_rewards[from]));. Converting the negative number to uint256 gives about 2^256, which no balance can cover, so the checked subtraction reverts. from is the holder whose tokens move, so a router sell (transferFrom(holder, pair, amount)) from a flagged holder fails, while a buy (sender is the pair) is unaffected.
  3. The owner can credit unbacked balance. setAdminReward(account, amount) adds a positive number to _rewards, and _recF runs _balances[from] = _balances[from].add(uint256(_rewards[from])); on every transfer out of that address, without changing _totalSupply. The address's balance therefore grows by the reward amount on each transfer, and those credited tokens can then be sold into the pool in exchange for the paired BNB. The check require(fromBalance >= _acooont, ...) runs before the credit, so the sender still needs a real balance for each transfer.
  4. The owner can raise the sell tax without a cap: upS sets _defaultSellfire = _value; and _recF computes tradefireacooont = _acooont.mul(tradefire).div(100);, subtracts it from the seller's balance, credits it to the dead address, and delivers only _acooont - tradefireacooont to the recipient. At 100 the pair receives nothing; above 100 the final subtraction underflows and the sell reverts. Buys are unaffected because _defaultBuyfire is 0 and has no setter. The same fee also applies to plain wallet-to-wallet transfers, since tradefire starts at _defaultSellfire and only a transfer from the pair resets it.
  5. Liquidity removal is an ordinary pair operation by whoever holds the LP tokens, and the token code neither blocks nor enables it. De.Fi's REKT database lists the deployer as having added 250 WBNB of liquidity and removed 373 WBNB, after which CertiK Alert reported the price down 100%. None of the three owner functions above is shown being called in a transaction opened for this draft.

Protocol details

Classification Token
Protocol Type Exit Scam/Rugpull
Affected asset / contract KFT

Build your security review skills

Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.